summaryrefslogtreecommitdiff
path: root/app/[lng]/procurement
diff options
context:
space:
mode:
Diffstat (limited to 'app/[lng]/procurement')
-rw-r--r--app/[lng]/procurement/(procurement)/b-rfq/[id]/final/page.tsx0
-rw-r--r--app/[lng]/procurement/(procurement)/b-rfq/[id]/initial/page.tsx52
-rw-r--r--app/[lng]/procurement/(procurement)/b-rfq/[id]/layout.tsx87
-rw-r--r--app/[lng]/procurement/(procurement)/b-rfq/[id]/page.tsx53
-rw-r--r--app/[lng]/procurement/(procurement)/b-rfq/page.tsx79
-rw-r--r--app/[lng]/procurement/(procurement)/basic-contract-template/page.tsx74
-rw-r--r--app/[lng]/procurement/(procurement)/basic-contract/page.tsx74
-rw-r--r--app/[lng]/procurement/(procurement)/bqcbe/page.tsx74
-rw-r--r--app/[lng]/procurement/(procurement)/bqtbe/page.tsx72
-rw-r--r--app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/cbe/page.tsx56
-rw-r--r--app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/layout.tsx90
-rw-r--r--app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/page.tsx57
-rw-r--r--app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/tbe/page.tsx55
-rw-r--r--app/[lng]/procurement/(procurement)/budgetary-rfq/page.tsx86
-rw-r--r--app/[lng]/procurement/(procurement)/budgetary/[id]/cbe/page.tsx56
-rw-r--r--app/[lng]/procurement/(procurement)/budgetary/[id]/layout.tsx90
-rw-r--r--app/[lng]/procurement/(procurement)/budgetary/[id]/page.tsx57
-rw-r--r--app/[lng]/procurement/(procurement)/budgetary/[id]/tbe/page.tsx55
-rw-r--r--app/[lng]/procurement/(procurement)/budgetary/page.tsx86
-rw-r--r--app/[lng]/procurement/(procurement)/dashboard/page.tsx17
-rw-r--r--app/[lng]/procurement/(procurement)/equip-class/page.tsx75
-rw-r--r--app/[lng]/procurement/(procurement)/esg-check-list/page.tsx74
-rw-r--r--app/[lng]/procurement/(procurement)/evaluation-check-list/page.tsx81
-rw-r--r--app/[lng]/procurement/(procurement)/evaluation-input/[id]/page.tsx22
-rw-r--r--app/[lng]/procurement/(procurement)/evaluation-input/page.tsx135
-rw-r--r--app/[lng]/procurement/(procurement)/evaluation-target-list/page.tsx118
-rw-r--r--app/[lng]/procurement/(procurement)/evaluation/page.tsx181
-rw-r--r--app/[lng]/procurement/(procurement)/faq/manage/actions.ts48
-rw-r--r--app/[lng]/procurement/(procurement)/faq/manage/page.tsx38
-rw-r--r--app/[lng]/procurement/(procurement)/faq/page.tsx62
-rw-r--r--app/[lng]/procurement/(procurement)/incoterms/page.tsx53
-rw-r--r--app/[lng]/procurement/(procurement)/items-tech/layout.tsx38
-rw-r--r--app/[lng]/procurement/(procurement)/items-tech/page.tsx67
-rw-r--r--app/[lng]/procurement/(procurement)/items/page.tsx68
-rw-r--r--app/[lng]/procurement/(procurement)/layout.tsx18
-rw-r--r--app/[lng]/procurement/(procurement)/menu-list/page.tsx70
-rw-r--r--app/[lng]/procurement/(procurement)/payment-conditions/page.tsx53
-rw-r--r--app/[lng]/procurement/(procurement)/po-rfq/page.tsx61
-rw-r--r--app/[lng]/procurement/(procurement)/po/page.tsx65
-rw-r--r--app/[lng]/procurement/(procurement)/poa/page.tsx61
-rw-r--r--app/[lng]/procurement/(procurement)/pq-criteria/[pqListId]/page.tsx68
-rw-r--r--app/[lng]/procurement/(procurement)/pq-criteria/page.tsx61
-rw-r--r--app/[lng]/procurement/(procurement)/pq_new/[vendorId]/[submissionId]/page.tsx206
-rw-r--r--app/[lng]/procurement/(procurement)/pq_new/page.tsx99
-rw-r--r--app/[lng]/procurement/(procurement)/project-gtc/page.tsx63
-rw-r--r--app/[lng]/procurement/(procurement)/project-vendors/page.tsx74
-rw-r--r--app/[lng]/procurement/(procurement)/projects/page.tsx75
-rw-r--r--app/[lng]/procurement/(procurement)/report/page.tsx105
-rw-r--r--app/[lng]/procurement/(procurement)/rfq/[id]/cbe/page.tsx55
-rw-r--r--app/[lng]/procurement/(procurement)/rfq/[id]/layout.tsx89
-rw-r--r--app/[lng]/procurement/(procurement)/rfq/[id]/page.tsx55
-rw-r--r--app/[lng]/procurement/(procurement)/rfq/[id]/tbe/page.tsx55
-rw-r--r--app/[lng]/procurement/(procurement)/rfq/page.tsx80
-rw-r--r--app/[lng]/procurement/(procurement)/settings/layout.tsx68
-rw-r--r--app/[lng]/procurement/(procurement)/settings/page.tsx18
-rw-r--r--app/[lng]/procurement/(procurement)/settings/preferences/page.tsx17
-rw-r--r--app/[lng]/procurement/(procurement)/system/admin-users/page.tsx60
-rw-r--r--app/[lng]/procurement/(procurement)/system/layout.tsx80
-rw-r--r--app/[lng]/procurement/(procurement)/system/page.tsx56
-rw-r--r--app/[lng]/procurement/(procurement)/system/password-policy/page.tsx63
-rw-r--r--app/[lng]/procurement/(procurement)/system/permissions/page.tsx17
-rw-r--r--app/[lng]/procurement/(procurement)/system/roles/page.tsx68
-rw-r--r--app/[lng]/procurement/(procurement)/tbe/page.tsx113
-rw-r--r--app/[lng]/procurement/(procurement)/vendor-candidates/page.tsx78
-rw-r--r--app/[lng]/procurement/(procurement)/vendor-check-list/page.tsx74
-rw-r--r--app/[lng]/procurement/(procurement)/vendor-investigation/page.tsx65
-rw-r--r--app/[lng]/procurement/(procurement)/vendor-type/page.tsx70
-rw-r--r--app/[lng]/procurement/(procurement)/vendors/[id]/info/items/page.tsx56
-rw-r--r--app/[lng]/procurement/(procurement)/vendors/[id]/info/layout.tsx94
-rw-r--r--app/[lng]/procurement/(procurement)/vendors/[id]/info/materials/page.tsx56
-rw-r--r--app/[lng]/procurement/(procurement)/vendors/[id]/info/page.tsx56
-rw-r--r--app/[lng]/procurement/(procurement)/vendors/[id]/info/rfq-history/page.tsx55
-rw-r--r--app/[lng]/procurement/(procurement)/vendors/page.tsx78
-rw-r--r--app/[lng]/procurement/page.tsx21
74 files changed, 0 insertions, 5006 deletions
diff --git a/app/[lng]/procurement/(procurement)/b-rfq/[id]/final/page.tsx b/app/[lng]/procurement/(procurement)/b-rfq/[id]/final/page.tsx
deleted file mode 100644
index e69de29b..00000000
--- a/app/[lng]/procurement/(procurement)/b-rfq/[id]/final/page.tsx
+++ /dev/null
diff --git a/app/[lng]/procurement/(procurement)/b-rfq/[id]/initial/page.tsx b/app/[lng]/procurement/(procurement)/b-rfq/[id]/initial/page.tsx
deleted file mode 100644
index 1af65fbc..00000000
--- a/app/[lng]/procurement/(procurement)/b-rfq/[id]/initial/page.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { InitialRfqDetailTable } from "@/lib/b-rfq/initial/initial-rfq-detail-table"
-import { getInitialRfqDetail } from "@/lib/b-rfq/service"
-import { searchParamsInitialRfqDetailCache } from "@/lib/b-rfq/validations"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsInitialRfqDetailCache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = getInitialRfqDetail({
- ...search,
- filters: validFilters,
- }, idAsNumber)
-
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- Initial RFQ List
- </h3>
- <p className="text-sm text-muted-foreground">
- 설계로부터 받은 RFQ 문서와 구매 RFQ 문서 및 사전 계약자료를 Vendor에 발송하기 위한 RFQ 생성 및 관리하는 화면입니다.
- </p>
- </div>
- <Separator />
- <div>
- <InitialRfqDetailTable promises={promises} rfqId={idAsNumber}/>
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/b-rfq/[id]/layout.tsx b/app/[lng]/procurement/(procurement)/b-rfq/[id]/layout.tsx
deleted file mode 100644
index d6836437..00000000
--- a/app/[lng]/procurement/(procurement)/b-rfq/[id]/layout.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import { Metadata } from "next"
-import Link from "next/link"
-import { Separator } from "@/components/ui/separator"
-import { SidebarNav } from "@/components/layout/sidebar-nav"
-import { formatDate } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
-import { ArrowLeft } from "lucide-react"
-import { RfqDashboardView } from "@/db/schema"
-import { findBRfqById } from "@/lib/b-rfq/service"
-
-export const metadata: Metadata = {
- title: "견적 RFQ 상세",
-}
-
-export default async function RfqLayout({
- children,
- params,
-}: {
- children: React.ReactNode
- params: { lng: string, id: string }
-}) {
-
- // 1) URL 파라미터에서 id 추출, Number로 변환
- const resolvedParams = await params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
- // 2) DB에서 해당 협력업체 정보 조회
- const rfq: RfqDashboardView | null = await findBRfqById(idAsNumber)
-
- // 3) 사이드바 메뉴
- const sidebarNavItems = [
- {
- title: "견적/입찰 문서관리",
- href: `/${lng}/evcp/b-rfq/${id}`,
- },
- {
- title: "Initial RFQ 발송",
- href: `/${lng}/evcp/b-rfq/${id}/initial`,
- },
- {
- title: "Final RFQ 발송",
- href: `/${lng}/evcp/b-rfq/${id}/final`,
- },
-
- ]
-
- return (
- <>
- <div className="container py-6">
- <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
- <div className="hidden space-y-6 p-10 pb-16 md:block">
- <div className="flex items-center justify-end mb-4">
- <Link href={`/${lng}/evcp/b-rfq`} passHref>
- <Button variant="ghost" className="flex items-center text-primary hover:text-primary/80 transition-colors p-0 h-auto">
- <ArrowLeft className="mr-1 h-4 w-4" />
- <span>RFQ 목록으로 돌아가기</span>
- </Button>
- </Link>
- </div>
- <div className="space-y-0.5">
- {/* 4) 협력업체 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */}
- <h2 className="text-2xl font-bold tracking-tight">
- {rfq
- ? `${rfq.rfqCode ?? ""} | ${rfq.packageNo ?? ""} | ${rfq.packageName ?? ""}`
- : "Loading RFQ..."}
- </h2>
-
- <p className="text-muted-foreground">
- PR발행 전 RFQ를 생성하여 관리하는 화면입니다.
- </p>
- <h3>Due Date:{rfq && rfq?.dueDate && <strong>{formatDate(rfq?.dueDate, "KR")}</strong>}</h3>
- </div>
- <Separator className="my-6" />
- <div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
- <aside className="lg:w-64 flex-shrink-0">
- <SidebarNav items={sidebarNavItems} />
- </aside>
- <div className="lg:w-[calc(100%-16rem)] overflow-auto">{children}</div>
- </div>
- </div>
- </section>
- </div>
- </>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/b-rfq/[id]/page.tsx b/app/[lng]/procurement/(procurement)/b-rfq/[id]/page.tsx
deleted file mode 100644
index 26dc45fb..00000000
--- a/app/[lng]/procurement/(procurement)/b-rfq/[id]/page.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { searchParamsRfqAttachmentsCache } from "@/lib/b-rfq/validations"
-import { getRfqAttachments } from "@/lib/b-rfq/service"
-import { RfqAttachmentsTable } from "@/lib/b-rfq/attachment/attachment-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsRfqAttachmentsCache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = getRfqAttachments({
- ...search,
- filters: validFilters,
- }, idAsNumber)
-
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- 견적 RFQ 문서관리
- </h3>
- <p className="text-sm text-muted-foreground">
- 설계로부터 받은 RFQ 문서와 구매 RFQ 문서를 관리하고 Vendor 회신을 점검/관리하는 화면입니다.
- </p>
- </div>
- <Separator />
- <div>
- <RfqAttachmentsTable promises={promises} rfqId={idAsNumber} />
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/b-rfq/page.tsx b/app/[lng]/procurement/(procurement)/b-rfq/page.tsx
deleted file mode 100644
index a66d7b58..00000000
--- a/app/[lng]/procurement/(procurement)/b-rfq/page.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import * as React from "react"
-import { Metadata } from "next"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { Shell } from "@/components/shell"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { searchParamsRFQDashboardCache } from "@/lib/b-rfq/validations"
-import { getRFQDashboard } from "@/lib/b-rfq/service"
-import { RFQDashboardTable } from "@/lib/b-rfq/summary-table/summary-rfq-table"
-
-export const metadata: Metadata = {
- title: "견적 RFQ",
- description: "",
-}
-
-interface PQReviewPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function PQReviewPage(props: PQReviewPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsRFQDashboardCache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- // 기본 필터 처리 (통일된 이름 사용)
- let basicFilters = []
- if (search.basicFilters && search.basicFilters.length > 0) {
- basicFilters = search.basicFilters
- console.log("Using search.basicFilters:", basicFilters);
- } else {
- console.log("No basic filters found");
- }
-
- // 모든 필터를 합쳐서 처리
- const allFilters = [...validFilters, ...basicFilters]
-
- // 조인 연산자도 통일된 이름 사용
- const joinOperator = search.basicJoinOperator || search.joinOperator || 'and';
-
- // Promise.all로 감싸서 전달
- const promises = Promise.all([
- getRFQDashboard({
- ...search,
- filters: allFilters,
- joinOperator,
- })
- ])
-
- console.log(search, "견적")
-
- return (
- <Shell className="gap-4">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 견적 RFQ
- </h2>
- </div>
- </div>
- </div>
-
- {/* Items처럼 직접 테이블 렌더링 */}
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={8}
- searchableColumnCount={2}
- filterableColumnCount={3}
- cellWidths={["10rem", "15rem", "12rem", "12rem", "8rem", "8rem", "10rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <RFQDashboardTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/basic-contract-template/page.tsx b/app/[lng]/procurement/(procurement)/basic-contract-template/page.tsx
deleted file mode 100644
index 26108323..00000000
--- a/app/[lng]/procurement/(procurement)/basic-contract-template/page.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { getBasicContractTemplates } from "@/lib/basic-contract/service"
-import { searchParamsTemplatesCache } from "@/lib/basic-contract/validations"
-import { BasicContractTemplateTable } from "@/lib/basic-contract/template/basic-contract-template"
-
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsTemplatesCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getBasicContractTemplates({
- ...search,
- filters: validFilters,
- }),
-
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 기본 계약문서 관리
- </h2>
- {/* <p className="text-muted-foreground">
- 기본계약서를 비롯하여 초기 서명이 필요한 문서를 등록하고 편집할 수 있습니다. 활성화된 템플릿이 서명 요청의 리스트에 나타나게 됩니다..{" "}
- <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span>
- 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다.
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <BasicContractTemplateTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/basic-contract/page.tsx b/app/[lng]/procurement/(procurement)/basic-contract/page.tsx
deleted file mode 100644
index 19211d4e..00000000
--- a/app/[lng]/procurement/(procurement)/basic-contract/page.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { getBasicContracts } from "@/lib/basic-contract/service"
-import { searchParamsCache } from "@/lib/basic-contract/validations"
-import { BasicContractsTable } from "@/lib/basic-contract/status/basic-contract-table"
-
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getBasicContracts({
- ...search,
- filters: validFilters,
- }),
-
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 기본계약서 서명 현황
- </h2>
- {/* <p className="text-muted-foreground">
- 기본계약서를 비롯하여 초기 서명이 필요한 문서의 서명 현황을 확인할 수 있고 서명된 문서들을 다운로드할 수 있습니다. {" "}
- <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span>
- 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다.
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <BasicContractsTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/bqcbe/page.tsx b/app/[lng]/procurement/(procurement)/bqcbe/page.tsx
deleted file mode 100644
index 831bb5a8..00000000
--- a/app/[lng]/procurement/(procurement)/bqcbe/page.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { getAllCBE } from "@/lib/rfqs/service"
-import { searchParamsCBECache } from "@/lib/rfqs/validations"
-
-import { AllCbeTable } from "@/lib/cbe/table/cbe-table"
-
-import { RfqType } from "@/lib/rfqs/validations"
-import * as React from "react"
-import { Shell } from "@/components/shell"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- }
- searchParams: Promise<SearchParams>
- rfqType: RfqType
-}
-
-export default async function RfqCBEPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
-
- const rfqType = props.rfqType ?? RfqType.BUDGETARY // rfqType이 없으면 BUDGETARY로 설정
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsCBECache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getAllCBE({
- ...search,
- filters: validFilters,
- rfqType
- }
- )
- ])
-
- // 4) 렌더링
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- CBE 관리
- </h2>
- {/* <p className="text-muted-foreground">
- 초대된 협력업체에게 CBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 CBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <AllCbeTable promises={promises}/>
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/bqtbe/page.tsx b/app/[lng]/procurement/(procurement)/bqtbe/page.tsx
deleted file mode 100644
index 3e56cfaa..00000000
--- a/app/[lng]/procurement/(procurement)/bqtbe/page.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { getAllTBE } from "@/lib/rfqs/service"
-import { searchParamsTBECache } from "@/lib/rfqs/validations"
-import { AllTbeTable } from "@/lib/tbe/table/tbe-table"
-import { RfqType } from "@/lib/rfqs/validations"
-import * as React from "react"
-import { Shell } from "@/components/shell"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- }
- searchParams: Promise<SearchParams>
- rfqType: RfqType
-}
-
-export default async function RfqTBEPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
-
- const rfqType = props.rfqType ?? RfqType.BUDGETARY // rfqType이 없으면 BUDGETARY로 설정
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsTBECache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getAllTBE({
- ...search,
- filters: validFilters,
- rfqType
- }
- )
- ])
-
- // 4) 렌더링
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- TBE 관리
- </h2>
- {/* <p className="text-muted-foreground">
- 초대된 협력업체에게 TBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <AllTbeTable promises={promises}/>
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/cbe/page.tsx b/app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/cbe/page.tsx
deleted file mode 100644
index 956facd3..00000000
--- a/app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/cbe/page.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { getCBE, getTBE } from "@/lib/rfqs/service"
-import { searchParamsCBECache, } from "@/lib/rfqs/validations"
-import { TbeTable } from "@/lib/rfqs/tbe-table/tbe-table"
-import { CbeTable } from "@/lib/rfqs/cbe-table/cbe-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqTBEPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsCBECache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getCBE({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
-
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- Commercial Bid Evaluation
- </h3>
- <p className="text-sm text-muted-foreground">
- 초대된 협력업체에게 CBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 CBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p>
- </div>
- <Separator />
- <div>
- <CbeTable promises={promises} rfqId={idAsNumber}/>
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/layout.tsx b/app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/layout.tsx
deleted file mode 100644
index 2b80e64f..00000000
--- a/app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/layout.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { Metadata } from "next"
-import Link from "next/link"
-import { ArrowLeft } from "lucide-react"
-
-import { Separator } from "@/components/ui/separator"
-import { SidebarNav } from "@/components/layout/sidebar-nav"
-import { RfqViewWithItems } from "@/db/schema/rfq"
-import { findRfqById } from "@/lib/rfqs/service"
-import { formatDate } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
-
-export const metadata: Metadata = {
- title: "Vendor Detail",
-}
-
-export default async function RfqLayout({
- children,
- params,
-}: {
- children: React.ReactNode
- params: { lng: string, id: string }
-}) {
-
- // 1) URL 파라미터에서 id 추출, Number로 변환
- const resolvedParams = await params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
- // 2) DB에서 해당 협력업체 정보 조회
- const rfq: RfqViewWithItems | null = await findRfqById(idAsNumber)
-
- // 3) 사이드바 메뉴
- const sidebarNavItems = [
- {
- title: "Matched Vendors",
- href: `/${lng}/evcp/budgetary/${id}`,
- },
- {
- title: "TBE",
- href: `/${lng}/evcp/budgetary/${id}/tbe`,
- },
- {
- title: "CBE",
- href: `/${lng}/evcp/budgetary/${id}/cbe`,
- },
-
- ]
-
- return (
- <>
- <div className="container py-6">
- <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
- <div className="hidden space-y-6 p-10 pb-16 md:block">
- <div className="flex items-center justify-end mb-4">
- <Link href={`/${lng}/evcp/budgetary-rfq`} passHref>
- <Button variant="ghost" className="flex items-center text-primary hover:text-primary/80 transition-colors p-0 h-auto">
- <ArrowLeft className="mr-1 h-4 w-4" />
- <span>Budgetary RFQ 목록으로 돌아가기</span>
- </Button>
- </Link>
- </div>
- <div className="space-y-0.5">
- {/* 4) 협력업체 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */}
- <h2 className="text-2xl font-bold tracking-tight">
- {rfq
- ? `${rfq.projectCode ?? ""} ${rfq.rfqCode ?? ""} 관리`
- : "Loading RFQ..."}
- </h2>
-
- <p className="text-muted-foreground">
- {rfq
- ? `${rfq.description ?? ""} ${rfq.lines.map(line => line.itemCode).join(", ")}`
- : ""}
- </p>
- <h3>Due Date:{rfq && rfq?.dueDate && <strong>{formatDate(rfq?.dueDate, "KR")}</strong>}</h3>
- </div>
- <Separator className="my-6" />
- <div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
- <aside className="lg:w-64 flex-shrink-0">
- <SidebarNav items={sidebarNavItems} />
- </aside>
- <div className="lg:w-[calc(100%-16rem)] overflow-auto">{children}</div>
- </div>
- </div>
- </section>
- </div>
- </>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/page.tsx b/app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/page.tsx
deleted file mode 100644
index dd9df563..00000000
--- a/app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/page.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { getMatchedVendors } from "@/lib/rfqs/service"
-import { searchParamsMatchedVCache } from "@/lib/rfqs/validations"
-import { MatchedVendorsTable } from "@/lib/rfqs/vendor-table/vendors-table"
-import { RfqType } from "@/lib/rfqs/validations"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
- rfqType: RfqType
-}
-
-export default async function RfqPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
- const rfqType = props.rfqType ?? RfqType.BUDGETARY // rfqType이 없으면 BUDGETARY로 설정
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- const searchParams = await props.searchParams
- const search = searchParamsMatchedVCache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getMatchedVendors({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
-
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- Vendors
- </h3>
- <p className="text-sm text-muted-foreground">
- 등록된 협력업체 중에서 이 RFQ 아이템에 매칭되는 업체를 보여줍니다. <br/>"발행하기" 버튼을 통해 RFQ를 전송하면 첨부파일과 함께 RFQ 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p>
- </div>
- <Separator />
- <div>
- <MatchedVendorsTable promises={promises} rfqId={idAsNumber} rfqType={rfqType}/>
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/tbe/page.tsx b/app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/tbe/page.tsx
deleted file mode 100644
index ec894e1c..00000000
--- a/app/[lng]/procurement/(procurement)/budgetary-rfq/[id]/tbe/page.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { getTBE } from "@/lib/rfqs/service"
-import { searchParamsTBECache } from "@/lib/rfqs/validations"
-import { TbeTable } from "@/lib/rfqs/tbe-table/tbe-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqTBEPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsTBECache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getTBE({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
-
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- Technical Bid Evaluation
- </h3>
- <p className="text-sm text-muted-foreground">
- 초대된 협력업체에게 TBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p>
- </div>
- <Separator />
- <div>
- <TbeTable promises={promises} rfqId={idAsNumber}/>
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/budgetary-rfq/page.tsx b/app/[lng]/procurement/(procurement)/budgetary-rfq/page.tsx
deleted file mode 100644
index f342bbff..00000000
--- a/app/[lng]/procurement/(procurement)/budgetary-rfq/page.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-
-import { searchParamsCache } from "@/lib/rfqs/validations"
-import { getRfqs, getRfqStatusCounts } from "@/lib/rfqs/service"
-import { RfqsTable } from "@/lib/rfqs/table/rfqs-table"
-import { getAllItems } from "@/lib/items/service"
-import { RfqType } from "@/lib/rfqs/validations"
-import { Ellipsis } from "lucide-react"
-
-interface RfqPageProps {
- searchParams: Promise<SearchParams>;
- rfqType: RfqType;
- title: string;
- description: string;
-}
-
-export default async function RfqPage({
- searchParams,
- rfqType = RfqType.PURCHASE_BUDGETARY,
- title = "Budgetary Quote",
- description = "Budgetary Quote를 등록하여 요청 및 응답을 관리할 수 있습니다."
-}: RfqPageProps) {
- const search = searchParamsCache.parse(await searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getRfqs({
- ...search,
- filters: validFilters,
- rfqType // 전달받은 rfqType 사용
- }),
- getRfqStatusCounts(rfqType), // rfqType 전달
- getAllItems()
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- {title}
- </h2>
- {/* <p className="text-muted-foreground">
- {description}
- 기본적인 정보와 RFQ를 위한 아이템 등록 및 첨부를 한 후,
- <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span> 을 클릭하면 "Proceed"를 통해 상세화면으로 이동하여 진행할 수 있습니다.
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <RfqsTable promises={promises} rfqType={rfqType} />
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/budgetary/[id]/cbe/page.tsx b/app/[lng]/procurement/(procurement)/budgetary/[id]/cbe/page.tsx
deleted file mode 100644
index 956facd3..00000000
--- a/app/[lng]/procurement/(procurement)/budgetary/[id]/cbe/page.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { getCBE, getTBE } from "@/lib/rfqs/service"
-import { searchParamsCBECache, } from "@/lib/rfqs/validations"
-import { TbeTable } from "@/lib/rfqs/tbe-table/tbe-table"
-import { CbeTable } from "@/lib/rfqs/cbe-table/cbe-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqTBEPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsCBECache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getCBE({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
-
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- Commercial Bid Evaluation
- </h3>
- <p className="text-sm text-muted-foreground">
- 초대된 협력업체에게 CBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 CBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p>
- </div>
- <Separator />
- <div>
- <CbeTable promises={promises} rfqId={idAsNumber}/>
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/budgetary/[id]/layout.tsx b/app/[lng]/procurement/(procurement)/budgetary/[id]/layout.tsx
deleted file mode 100644
index d58d8363..00000000
--- a/app/[lng]/procurement/(procurement)/budgetary/[id]/layout.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { Metadata } from "next"
-import Link from "next/link"
-import { ArrowLeft } from "lucide-react"
-import { Separator } from "@/components/ui/separator"
-import { SidebarNav } from "@/components/layout/sidebar-nav"
-import { RfqViewWithItems } from "@/db/schema/rfq"
-import { findRfqById } from "@/lib/rfqs/service"
-import { formatDate } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
-
-export const metadata: Metadata = {
- title: "Vendor Detail",
-}
-
-export default async function RfqLayout({
- children,
- params,
-}: {
- children: React.ReactNode
- params: { lng: string, id: string }
-}) {
-
- // 1) URL 파라미터에서 id 추출, Number로 변환
- const resolvedParams = await params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
- // 2) DB에서 해당 협력업체 정보 조회
- const rfq: RfqViewWithItems | null = await findRfqById(idAsNumber)
-
- // 3) 사이드바 메뉴
- const sidebarNavItems = [
- {
- title: "Matched Vendors",
- href: `/${lng}/evcp/budgetary/${id}`,
- },
- {
- title: "TBE",
- href: `/${lng}/evcp/budgetary/${id}/tbe`,
- },
- {
- title: "CBE",
- href: `/${lng}/evcp/budgetary/${id}/cbe`,
- },
- ]
-
- return (
- <>
- <div className="container py-6">
- <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
- <div className="hidden space-y-6 p-10 pb-16 md:block">
- {/* RFQ 목록으로 돌아가는 링크 추가 */}
- <div className="flex items-center justify-end mb-4">
- <Link href={`/${lng}/evcp/budgetary`} passHref>
- <Button variant="ghost" className="flex items-center text-primary hover:text-primary/80 transition-colors p-0 h-auto">
- <ArrowLeft className="mr-1 h-4 w-4" />
- <span>Budgetary Quote 목록으로 돌아가기</span>
- </Button>
- </Link>
- </div>
-
- <div className="space-y-0.5">
- {/* 4) 협력업체 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */}
- <h2 className="text-2xl font-bold tracking-tight">
- {rfq
- ? `${rfq.projectCode ?? ""} ${rfq.rfqCode ?? ""} 관리`
- : "Loading RFQ..."}
- </h2>
-
- <p className="text-muted-foreground">
- {rfq
- ? `${rfq.description ?? ""} ${rfq.lines.map(line => line.itemCode).join(", ")}`
- : ""}
- </p>
- <h3>Due Date:{rfq && rfq?.dueDate && <strong>{formatDate(rfq?.dueDate, "KR")}</strong>}</h3>
- </div>
- <Separator className="my-6" />
- <div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
- <aside className="lg:w-64 flex-shrink-0">
- <SidebarNav items={sidebarNavItems} />
- </aside>
- <div className="lg:w-[calc(100%-16rem)] overflow-auto">{children}</div>
- </div>
- </div>
- </section>
- </div>
- </>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/budgetary/[id]/page.tsx b/app/[lng]/procurement/(procurement)/budgetary/[id]/page.tsx
deleted file mode 100644
index dd9df563..00000000
--- a/app/[lng]/procurement/(procurement)/budgetary/[id]/page.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { getMatchedVendors } from "@/lib/rfqs/service"
-import { searchParamsMatchedVCache } from "@/lib/rfqs/validations"
-import { MatchedVendorsTable } from "@/lib/rfqs/vendor-table/vendors-table"
-import { RfqType } from "@/lib/rfqs/validations"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
- rfqType: RfqType
-}
-
-export default async function RfqPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
- const rfqType = props.rfqType ?? RfqType.BUDGETARY // rfqType이 없으면 BUDGETARY로 설정
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- const searchParams = await props.searchParams
- const search = searchParamsMatchedVCache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getMatchedVendors({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
-
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- Vendors
- </h3>
- <p className="text-sm text-muted-foreground">
- 등록된 협력업체 중에서 이 RFQ 아이템에 매칭되는 업체를 보여줍니다. <br/>"발행하기" 버튼을 통해 RFQ를 전송하면 첨부파일과 함께 RFQ 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p>
- </div>
- <Separator />
- <div>
- <MatchedVendorsTable promises={promises} rfqId={idAsNumber} rfqType={rfqType}/>
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/budgetary/[id]/tbe/page.tsx b/app/[lng]/procurement/(procurement)/budgetary/[id]/tbe/page.tsx
deleted file mode 100644
index ec894e1c..00000000
--- a/app/[lng]/procurement/(procurement)/budgetary/[id]/tbe/page.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { getTBE } from "@/lib/rfqs/service"
-import { searchParamsTBECache } from "@/lib/rfqs/validations"
-import { TbeTable } from "@/lib/rfqs/tbe-table/tbe-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqTBEPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsTBECache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getTBE({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
-
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- Technical Bid Evaluation
- </h3>
- <p className="text-sm text-muted-foreground">
- 초대된 협력업체에게 TBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p>
- </div>
- <Separator />
- <div>
- <TbeTable promises={promises} rfqId={idAsNumber}/>
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/budgetary/page.tsx b/app/[lng]/procurement/(procurement)/budgetary/page.tsx
deleted file mode 100644
index 15b4cdd4..00000000
--- a/app/[lng]/procurement/(procurement)/budgetary/page.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-
-import { searchParamsCache } from "@/lib/rfqs/validations"
-import { getRfqs, getRfqStatusCounts } from "@/lib/rfqs/service"
-import { RfqsTable } from "@/lib/rfqs/table/rfqs-table"
-import { getAllItems } from "@/lib/items/service"
-import { RfqType } from "@/lib/rfqs/validations"
-import { Ellipsis } from "lucide-react"
-
-interface RfqPageProps {
- searchParams: Promise<SearchParams>;
- rfqType: RfqType;
- title: string;
- description: string;
-}
-
-export default async function RfqPage({
- searchParams,
- rfqType = RfqType.BUDGETARY,
- title = "Budgetary Quote",
- description = "Budgetary Quote를 등록하여 요청 및 응답을 관리할 수 있습니다."
-}: RfqPageProps) {
- const search = searchParamsCache.parse(await searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getRfqs({
- ...search,
- filters: validFilters,
- rfqType // 전달받은 rfqType 사용
- }),
- getRfqStatusCounts(rfqType), // rfqType 전달
- getAllItems()
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- {title}
- </h2>
- {/* <p className="text-muted-foreground">
- {description}
- 기본적인 정보와 RFQ를 위한 아이템 등록 및 첨부를 한 후,
- <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span> 을 클릭하면 "Proceed"를 통해 상세화면으로 이동하여 진행할 수 있습니다.
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <RfqsTable promises={promises} rfqType={rfqType} />
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/dashboard/page.tsx b/app/[lng]/procurement/(procurement)/dashboard/page.tsx
deleted file mode 100644
index 1d61dc16..00000000
--- a/app/[lng]/procurement/(procurement)/dashboard/page.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-// app/invalid-access/page.tsx
-
-export default function InvalidAccessPage() {
- return (
- <main style={{ padding: '40px', textAlign: 'center' }}>
- <h1>부적절한 접근입니다</h1>
- <p>
- 협력업체(Vendor)가 EVCP 화면에 접속하거나 <br />
- SHI 계정이 협력업체 화면에 접속하려고 시도하는 경우입니다.
- </p>
- <p>
- <strong>접근 권한이 없으므로, 다른 화면으로 이동해 주세요.</strong>
- </p>
- </main>
- );
- }
- \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/equip-class/page.tsx b/app/[lng]/procurement/(procurement)/equip-class/page.tsx
deleted file mode 100644
index 34fd32b6..00000000
--- a/app/[lng]/procurement/(procurement)/equip-class/page.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { searchParamsCache } from "@/lib/equip-class/validation"
-import { FormListsTable } from "@/lib/form-list/table/formLists-table"
-import { getTagClassists } from "@/lib/equip-class/service"
-import { EquipClassTable } from "@/lib/equip-class/table/equipClass-table"
-
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getTagClassists({
- ...search,
- filters: validFilters,
- }),
-
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 객체 클래스 목록 from S-EDP
- </h2>
- {/* <p className="text-muted-foreground">
- 객체 클래스 목록을 확인할 수 있습니다.{" "}
- <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span>
- 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다.
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <EquipClassTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/esg-check-list/page.tsx b/app/[lng]/procurement/(procurement)/esg-check-list/page.tsx
deleted file mode 100644
index 8bccd3b7..00000000
--- a/app/[lng]/procurement/(procurement)/esg-check-list/page.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { getEsgEvaluations } from "@/lib/esg-check-list/service"
-import { getEsgEvaluationsSchema } from "@/lib/esg-check-list/validation"
-import { EsgEvaluationsTable } from "@/lib/esg-check-list/table/esg-table"
-
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = getEsgEvaluationsSchema.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getEsgEvaluations({
- ...search,
- filters: validFilters,
- }),
-
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- ESG 자가진단평가서 항목 관리
- </h2>
- {/* <p className="text-muted-foreground">
- 협력업체 평가에 사용되는 ESG 자가진단표를 관리{" "}
- <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span>
- 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다.
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <EsgEvaluationsTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/evaluation-check-list/page.tsx b/app/[lng]/procurement/(procurement)/evaluation-check-list/page.tsx
deleted file mode 100644
index 45da961b..00000000
--- a/app/[lng]/procurement/(procurement)/evaluation-check-list/page.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-/* IMPORT */
-import { DataTableSkeleton } from '@/components/data-table/data-table-skeleton';
-import { getRegEvalCriteria } from '@/lib/evaluation-criteria/service';
-import { getValidFilters } from '@/lib/data-table';
-import RegEvalCriteriaTable from '@/lib/evaluation-criteria/table/reg-eval-criteria-table';
-import { searchParamsCache } from '@/lib/evaluation-criteria/validations';
-import { Shell } from '@/components/shell';
-import { Skeleton } from '@/components/ui/skeleton';
-import { Suspense } from 'react';
-import { type SearchParams } from '@/types/table';
-
-// ----------------------------------------------------------------------------------------------------
-
-/* TYPES */
-interface EvaluationCriteriaPageProps {
- searchParams: Promise<SearchParams>
-}
-
-// ----------------------------------------------------------------------------------------------------
-
-/* REGULAR EVALUATION CRITERIA PAGE */
-async function EvaluationCriteriaPage(props: EvaluationCriteriaPageProps) {
- const searchParams = await props.searchParams;
- const search = searchParamsCache.parse(searchParams);
- const validFilters = getValidFilters(search.filters);
- const promises = Promise.all([
- getRegEvalCriteria({
- ...search,
- filters: validFilters,
- }),
- ]);
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 협력업체 평가기준표 관리
- </h2>
- {/* <p className="text-muted-foreground">
- 협력업체 평가에 사용되는 평가기준표를 관리{" "}
- <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span>
- 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다.
- </p> */}
- </div>
- </div>
- </div>
-
- <Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </Suspense>
- <Suspense
- fallback={
- <DataTableSkeleton
- columnCount={11}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <RegEvalCriteriaTable promises={promises} />
- </Suspense>
- </Shell>
- )
-}
-
-// ----------------------------------------------------------------------------------------------------
-
-/* EXPORT */
-export default EvaluationCriteriaPage; \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/evaluation-input/[id]/page.tsx b/app/[lng]/procurement/(procurement)/evaluation-input/[id]/page.tsx
deleted file mode 100644
index 3a403620..00000000
--- a/app/[lng]/procurement/(procurement)/evaluation-input/[id]/page.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { EvaluationPage } from "@/lib/evaluation-submit/evaluation-page"
-import { Metadata } from "next"
-
-export const metadata: Metadata = {
- title: "평가 작성",
- description: "협력업체 평가를 작성합니다",
-}
-
-interface PageProps {
- params: {
- id: string
- }
-}
-
-export default function Page({ params }: PageProps) {
- return <EvaluationPage />
-}
-
-export async function generateStaticParams() {
- // 동적 경로이므로 빈 배열 반환
- return []
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/evaluation-input/page.tsx b/app/[lng]/procurement/(procurement)/evaluation-input/page.tsx
deleted file mode 100644
index 00f1820f..00000000
--- a/app/[lng]/procurement/(procurement)/evaluation-input/page.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { getServerSession } from "next-auth"
-import { authOptions } from "@/app/api/auth/[...nextauth]/route"
-import Link from "next/link"
-import { Button } from "@/components/ui/button"
-import { LogIn } from "lucide-react"
-import { getSHIEvaluationSubmissions } from "@/lib/evaluation-submit/service"
-import { getSHIEvaluationsSubmitSchema } from "@/lib/evaluation-submit/validation"
-import { SHIEvaluationSubmissionsTable } from "@/lib/evaluation-submit/table/submit-table"
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = getSHIEvaluationsSubmitSchema.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- // Get session
- const session = await getServerSession(authOptions)
-
- // Check if user is logged in
- if (!session || !session.user) {
- // Return login required UI instead of redirecting
- return (
- <Shell className="gap-6">
- <div className="flex items-center justify-between">
- <div>
- <div className="flex items-center gap-2">
- <h2 className="text-2xl font-bold tracking-tight">
- 정기평가
- </h2>
- </div>
- {/* <p className="text-muted-foreground">
- 요청된 정기평가를 입력하고 제출할 수 있습니다.
- </p> */}
- </div>
- </div>
-
- <div className="flex flex-col items-center justify-center py-12 text-center">
- <div className="rounded-lg border border-dashed p-10 shadow-sm">
- <h3 className="mb-2 text-xl font-semibold">로그인이 필요합니다</h3>
- <p className="mb-6 text-muted-foreground">
- 정기평가를 확인하려면 먼저 로그인하세요.
- </p>
- <Button size="lg" asChild>
- <Link href="/partners">
- <LogIn className="mr-2 h-4 w-4" />
- 로그인하기
- </Link>
- </Button>
- </div>
- </div>
- </Shell>
- )
- }
-
- const userId = session.user.id
-
- // Validate vendorId (should be a number)
- const idAsNumber = Number(userId)
-
-
- if (isNaN(idAsNumber)) {
- // Handle invalid vendor ID (this shouldn't happen if authentication is working properly)
- return (
- <Shell className="gap-6">
- <div className="flex items-center justify-between">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 정기평가
- </h2>
- </div>
- </div>
- <div className="flex flex-col items-center justify-center py-12 text-center">
- <div className="rounded-lg border border-dashed p-10 shadow-sm">
- <h3 className="mb-2 text-xl font-semibold">계정 오류</h3>
- <p className="mb-6 text-muted-foreground">
- 관리자에게 문의하세요.
- </p>
- </div>
- </div>
- </Shell>
- )
- }
-
- // If we got here, we have a valid vendor ID
- const promises = Promise.all([
- getSHIEvaluationSubmissions({
- ...search,
- filters: validFilters,
- }, idAsNumber)
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 정기평가
- </h2>
- {/* <p className="text-muted-foreground">
- 요청된 정기평가를 입력하고 제출할 수 있습니다.
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* DateRangePicker can go here */}
- </React.Suspense>
-
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <SHIEvaluationSubmissionsTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/evaluation-target-list/page.tsx b/app/[lng]/procurement/(procurement)/evaluation-target-list/page.tsx
deleted file mode 100644
index a0523eea..00000000
--- a/app/[lng]/procurement/(procurement)/evaluation-target-list/page.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import * as React from "react"
-import { Metadata } from "next"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { Shell } from "@/components/shell"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { HelpCircle } from "lucide-react"
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover"
-import { Button } from "@/components/ui/button"
-import { Badge } from "@/components/ui/badge"
-
-import { getDefaultEvaluationYear, searchParamsEvaluationTargetsCache } from "@/lib/evaluation-target-list/validation"
-import { getEvaluationTargets } from "@/lib/evaluation-target-list/service"
-import { EvaluationTargetsTable } from "@/lib/evaluation-target-list/table/evaluation-target-table"
-import { InformationButton } from "@/components/information/information-button"
-export const metadata: Metadata = {
- title: "협력업체 평가 대상 관리",
- description: "협력업체 평가 대상을 확정하고 담당자를 지정합니다.",
-}
-
-interface EvaluationTargetsPageProps {
- searchParams: Promise<SearchParams>
-}
-
-
-
-export default async function EvaluationTargetsPage(props: EvaluationTargetsPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsEvaluationTargetsCache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- // 기본 필터 처리 (통일된 이름 사용)
- let basicFilters = []
- if (search.basicFilters && search.basicFilters.length > 0) {
- basicFilters = search.basicFilters
- console.log("Using search.basicFilters:", basicFilters);
- } else {
- console.log("No basic filters found");
- }
-
- // 모든 필터를 합쳐서 처리
- const allFilters = [...validFilters, ...basicFilters]
-
- // 조인 연산자도 통일된 이름 사용
- const joinOperator = search.basicJoinOperator || search.joinOperator || 'and';
-
- // 현재 평가년도 (필터에서 가져오거나 기본값 사용)
- const currentEvaluationYear = search.evaluationYear || getDefaultEvaluationYear()
-
- // Promise.all로 감싸서 전달
- const promises = Promise.all([
- getEvaluationTargets({
- ...search,
- filters: allFilters,
- joinOperator,
- })
- ])
-
- return (
- <Shell className="gap-4">
- {/* 간소화된 헤더 */}
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center gap-2">
- <div className="flex items-center gap-2">
- <h2 className="text-2xl font-bold tracking-tight">
- 협력업체 평가 대상 관리
- </h2>
- <InformationButton pagePath="evcp/evaluation-target-list" />
- </div>
- <Badge variant="outline" className="text-sm">
- {currentEvaluationYear}년도
- </Badge>
-
- </div>
- </div>
- </div>
-
- {/* 메인 테이블 (통계는 테이블 내부로 이동) */}
- <React.Suspense
- key={JSON.stringify(searchParams)} // URL 파라미터가 변경될 때마다 강제 리렌더링
- fallback={
- <DataTableSkeleton
- columnCount={12}
- searchableColumnCount={2}
- filterableColumnCount={6}
- cellWidths={[
- "3rem", // checkbox
- "5rem", // 평가년도
- "4rem", // 구분
- "8rem", // 벤더코드
- "12rem", // 벤더명
- "4rem", // 내외자
- "6rem", // 자재구분
- "5rem", // 상태
- "5rem", // 의견일치
- "8rem", // 담당자현황
- "10rem", // 관리자의견
- "8rem" // actions
- ]}
- shrinkZero
- />
- }
- >
- {currentEvaluationYear &&
- <EvaluationTargetsTable
- promises={promises}
- evaluationYear={currentEvaluationYear}
- />
-}
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/evaluation/page.tsx b/app/[lng]/procurement/(procurement)/evaluation/page.tsx
deleted file mode 100644
index 2d8cbed7..00000000
--- a/app/[lng]/procurement/(procurement)/evaluation/page.tsx
+++ /dev/null
@@ -1,181 +0,0 @@
-// ================================================================
-// 4. PERIODIC EVALUATIONS PAGE
-// ================================================================
-
-import * as React from "react"
-import { Metadata } from "next"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { Shell } from "@/components/shell"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { HelpCircle } from "lucide-react"
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover"
-import { Button } from "@/components/ui/button"
-import { Badge } from "@/components/ui/badge"
-import { PeriodicEvaluationsTable } from "@/lib/evaluation/table/evaluation-table"
-import { getPeriodicEvaluations } from "@/lib/evaluation/service"
-import { searchParamsEvaluationsCache } from "@/lib/evaluation/validation"
-
-export const metadata: Metadata = {
- title: "협력업체 정기평가",
- description: "협력업체 정기평가 진행 현황을 관리합니다.",
-}
-
-interface PeriodicEvaluationsPageProps {
- searchParams: Promise<SearchParams>
-}
-
-// 프로세스 안내 팝오버 컴포넌트
-function ProcessGuidePopover() {
- return (
- <Popover>
- <PopoverTrigger asChild>
- <Button variant="ghost" size="icon" className="h-6 w-6">
- <HelpCircle className="h-4 w-4 text-muted-foreground" />
- </Button>
- </PopoverTrigger>
- <PopoverContent className="w-96" align="start">
- <div className="space-y-3">
- <div className="space-y-1">
- <h4 className="font-medium">정기평가 프로세스</h4>
- {/* <p className="text-sm text-muted-foreground">
- 확정된 평가 대상 업체들에 대한 정기평가 절차입니다.
- </p> */}
- </div>
- <div className="space-y-3 text-sm">
- <div className="flex gap-3">
- <div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600">
- 1
- </div>
- <div>
- <p className="font-medium">평가 대상 확정</p>
- <p className="text-muted-foreground">평가 대상으로 확정된 업체들의 정기평가가 자동 생성됩니다.</p>
- </div>
- </div>
- <div className="flex gap-3">
- <div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600">
- 2
- </div>
- <div>
- <p className="font-medium">업체 자료 제출</p>
- <p className="text-muted-foreground">각 업체는 평가에 필요한 자료를 제출 마감일까지 제출해야 합니다.</p>
- </div>
- </div>
- <div className="flex gap-3">
- <div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600">
- 3
- </div>
- <div>
- <p className="font-medium">평가자 검토</p>
- <p className="text-muted-foreground">지정된 평가자들이 평가표를 기반으로 점수를 매기고 검토합니다.</p>
- </div>
- </div>
- <div className="flex gap-3">
- <div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600">
- 4
- </div>
- <div>
- <p className="font-medium">최종 확정</p>
- <p className="text-muted-foreground">모든 평가가 완료되면 최종 점수와 등급이 확정됩니다.</p>
- </div>
- </div>
- </div>
- </div>
- </PopoverContent>
- </Popover>
- )
-}
-
-// TODO: 이 함수들은 실제 서비스 파일에서 구현해야 함
-function getDefaultEvaluationYear() {
- return new Date().getFullYear()
-}
-
-
-
-export default async function PeriodicEvaluationsPage(props: PeriodicEvaluationsPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsEvaluationsCache.parse(searchParams)
- const validFilters = getValidFilters(search.filters || [])
-
- // 기본 필터 처리
- let basicFilters = []
- if (search.basicFilters && search.basicFilters.length > 0) {
- basicFilters = search.basicFilters
- }
-
- // 모든 필터를 합쳐서 처리
- const allFilters = [...validFilters, ...basicFilters]
-
- // 조인 연산자
- const joinOperator = search.basicJoinOperator || search.joinOperator || 'and';
-
- // 현재 평가년도
- const currentEvaluationYear = search.evaluationYear || getDefaultEvaluationYear()
-
- // Promise.all로 감싸서 전달
- const promises = Promise.all([
- getPeriodicEvaluations({
- ...search,
- filters: allFilters,
- joinOperator,
- })
- ])
-
- return (
- <Shell className="gap-4">
- {/* 헤더 */}
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center gap-2">
- <h2 className="text-2xl font-bold tracking-tight">
- 협력업체 정기평가
- </h2>
- <Badge variant="outline" className="text-sm">
- {currentEvaluationYear}년도
- </Badge>
- </div>
- </div>
- </div>
-
- {/* 메인 테이블 */}
- <React.Suspense
- key={JSON.stringify(searchParams)}
- fallback={
- <DataTableSkeleton
- columnCount={15}
- searchableColumnCount={2}
- filterableColumnCount={8}
- cellWidths={[
- "3rem", // checkbox
- "5rem", // 평가년도
- "5rem", // 평가기간
- "4rem", // 구분
- "8rem", // 벤더코드
- "12rem", // 벤더명
- "4rem", // 내외자
- "6rem", // 자재구분
- "5rem", // 문서제출
- "4rem", // 제출일
- "4rem", // 마감일
- "4rem", // 총점
- "4rem", // 등급
- "5rem", // 진행상태
- "8rem" // actions
- ]}
- shrinkZero
- />
- }
- >
- <PeriodicEvaluationsTable
- promises={promises}
- evaluationYear={currentEvaluationYear}
- />
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/faq/manage/actions.ts b/app/[lng]/procurement/(procurement)/faq/manage/actions.ts
deleted file mode 100644
index bc443a8a..00000000
--- a/app/[lng]/procurement/(procurement)/faq/manage/actions.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-'use server';
-
-import { promises as fs } from 'fs';
-import path from 'path';
-import { FaqCategory } from '@/components/faq/FaqCard';
-import { fallbackLng } from '@/i18n/settings';
-
-const FAQ_CONFIG_PATH = path.join(process.cwd(), 'config', 'faqDataConfig.ts');
-
-export async function updateFaqData(lng: string, newData: FaqCategory[]) {
- try {
- const fileContent = await fs.readFile(FAQ_CONFIG_PATH, 'utf-8');
- const dataMatch = fileContent.match(/export const faqCategories[^=]*=\s*(\{[\s\S]*\});/);
- if (!dataMatch) {
- throw new Error('FAQ 데이터 형식이 올바르지 않습니다.');
- }
-
- const allData = eval(`(${dataMatch[1]})`);
- const updatedData = {
- ...allData,
- [lng]: newData
- };
-
- const newFileContent = `import { FaqCategory } from "@/components/faq/FaqCard";\n\ninterface LocalizedFaqCategories {\n [lng: string]: FaqCategory[];\n}\n\nexport const faqCategories: LocalizedFaqCategories = ${JSON.stringify(updatedData, null, 4)};`;
- await fs.writeFile(FAQ_CONFIG_PATH, newFileContent, 'utf-8');
-
- return { success: true };
- } catch (error) {
- console.error('FAQ 데이터 업데이트 중 오류 발생:', error);
- return { success: false, error: '데이터 업데이트 중 오류가 발생했습니다.' };
- }
-}
-
-export async function getFaqData(lng: string): Promise<{ data: FaqCategory[] }> {
- try {
- const fileContent = await fs.readFile(FAQ_CONFIG_PATH, 'utf-8');
- const dataMatch = fileContent.match(/export const faqCategories[^=]*=\s*(\{[\s\S]*\});/);
- if (!dataMatch) {
- throw new Error('FAQ 데이터 형식이 올바르지 않습니다.');
- }
-
- const allData = eval(`(${dataMatch[1]})`);
- return { data: allData[lng] || allData[fallbackLng] || [] };
- } catch (error) {
- console.error('FAQ 데이터 읽기 중 오류 발생:', error);
- return { data: [] };
- }
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/faq/manage/page.tsx b/app/[lng]/procurement/(procurement)/faq/manage/page.tsx
deleted file mode 100644
index 011bbfa4..00000000
--- a/app/[lng]/procurement/(procurement)/faq/manage/page.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { FaqManager } from '@/components/faq/FaqManager';
-import { getFaqData, updateFaqData } from './actions';
-import { revalidatePath } from 'next/cache';
-import { FaqCategory } from '@/components/faq/FaqCard';
-
-interface Props {
- params: {
- lng: string;
- }
-}
-
-export default async function FaqManagePage(props: Props) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const { data } = await getFaqData(lng);
-
- async function handleSave(newData: FaqCategory[]) {
- 'use server';
- await updateFaqData(lng, newData);
- revalidatePath(`/${lng}/evcp/faq`);
- }
-
- return (
- <div className="container py-6">
- <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
- <div className="space-y-6 p-10 pb-16">
- <div className="space-y-0.5">
- <h2 className="text-2xl font-bold tracking-tight">FAQ Management</h2>
- <p className="text-muted-foreground">
- Manage FAQ categories and items for {lng.toUpperCase()} language.
- </p>
- </div>
- <FaqManager initialData={data} onSave={handleSave} lng={lng} />
- </div>
- </section>
- </div>
- );
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/faq/page.tsx b/app/[lng]/procurement/(procurement)/faq/page.tsx
deleted file mode 100644
index 00956591..00000000
--- a/app/[lng]/procurement/(procurement)/faq/page.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
-import { faqCategories } from "@/config/faqDataConfig"
-import { FaqCard } from "@/components/faq/FaqCard"
-import { Button } from "@/components/ui/button"
-import { Settings } from "lucide-react"
-import Link from "next/link"
-import { fallbackLng } from "@/i18n/settings"
-
-interface Props {
- params: {
- lng: string;
- }
-}
-
-export default async function FaqPage(props: Props) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const localizedFaqCategories = faqCategories[lng] || faqCategories[fallbackLng];
-
- return (
- <div className="container py-6">
- <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
- <div className="space-y-6 p-10 pb-16">
- <div className="flex justify-between items-center">
- <div className="space-y-0.5">
- <h2 className="text-2xl font-bold tracking-tight">FAQ</h2>
- {/* <p className="text-muted-foreground">
- Find answers to common questions about using the EVCP system.
- </p> */}
- </div>
- <Link href={`/${lng}/evcp/faq/manage`}>
- <Button variant="outline">
- <Settings className="w-4 h-4 mr-2" />
- FAQ 관리
- </Button>
- </Link>
- </div>
- <Separator className="my-6" />
-
- <Tabs defaultValue={localizedFaqCategories[0]?.label} className="space-y-4">
- <TabsList>
- {localizedFaqCategories.map((category) => (
- <TabsTrigger key={category.label} value={category.label}>
- {category.label}
- </TabsTrigger>
- ))}
- </TabsList>
-
- {localizedFaqCategories.map((category) => (
- <TabsContent key={category.label} value={category.label} className="space-y-4">
- {category.items.map((item, index) => (
- <FaqCard key={index} item={item} />
- ))}
- </TabsContent>
- ))}
- </Tabs>
- </div>
- </section>
- </div>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/incoterms/page.tsx b/app/[lng]/procurement/(procurement)/incoterms/page.tsx
deleted file mode 100644
index 804bc5af..00000000
--- a/app/[lng]/procurement/(procurement)/incoterms/page.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import * as React from "react";
-import { type SearchParams } from "@/types/table";
-import { getValidFilters } from "@/lib/data-table";
-import { Shell } from "@/components/shell";
-import { Skeleton } from "@/components/ui/skeleton";
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton";
-import { SearchParamsCache } from "@/lib/incoterms/validations";
-import { getIncoterms } from "@/lib/incoterms/service";
-import { IncotermsTable } from "@/lib/incoterms/table/incoterms-table";
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>;
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams;
- const search = SearchParamsCache.parse(searchParams);
- const validFilters = getValidFilters(search.filters);
-
- const promises = Promise.all([
- getIncoterms({
- ...search,
- filters: validFilters,
- }),
- ]);
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">인코텀즈 관리</h2>
- {/* <p className="text-muted-foreground">
- 인코텀즈(Incoterms)를 등록, 수정, 삭제할 수 있습니다.
- </p> */}
- </div>
- </div>
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}></React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={4}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <IncotermsTable promises={promises} />
- </React.Suspense>
- </Shell>
- );
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/items-tech/layout.tsx b/app/[lng]/procurement/(procurement)/items-tech/layout.tsx
deleted file mode 100644
index d375059b..00000000
--- a/app/[lng]/procurement/(procurement)/items-tech/layout.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import * as React from "react"
-import { ItemTechContainer } from "@/components/items-tech/item-tech-container"
-import { Shell } from "@/components/shell"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-
-// Layout 컴포넌트는 서버 컴포넌트입니다
-export default function ItemsShipLayout({
- children,
-}: {
- children: React.ReactNode
-}) {
- // 아이템 타입 정의
- const itemTypes = [
- { id: "ship", name: "조선 아이템" },
- { id: "top", name: "해양 TOP" },
- { id: "hull", name: "해양 HULL" },
- ]
-
- return (
- <Shell className="gap-4">
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <ItemTechContainer itemTypes={itemTypes}>
- {children}
- </ItemTechContainer>
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/items-tech/page.tsx b/app/[lng]/procurement/(procurement)/items-tech/page.tsx
deleted file mode 100644
index 55ac9c63..00000000
--- a/app/[lng]/procurement/(procurement)/items-tech/page.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { shipbuildingSearchParamsCache, offshoreTopSearchParamsCache, offshoreHullSearchParamsCache } from "@/lib/items-tech/validations"
-import { getShipbuildingItems, getOffshoreTopItems, getOffshoreHullItems } from "@/lib/items-tech/service"
-import { OffshoreTopTable } from "@/lib/items-tech/table/top/offshore-top-table"
-import { OffshoreHullTable } from "@/lib/items-tech/table/hull/offshore-hull-table"
-
-// 대소문자 문제 해결 - 실제 파일명에 맞게 import
-import { ItemsShipTable } from "@/lib/items-tech/table/ship/Items-ship-table"
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage({ searchParams }: IndexPageProps) {
- const params = await searchParams
- const shipbuildingSearch = shipbuildingSearchParamsCache.parse(params)
- const offshoreTopSearch = offshoreTopSearchParamsCache.parse(params)
- const offshoreHullSearch = offshoreHullSearchParamsCache.parse(params)
- const validShipbuildingFilters = getValidFilters(shipbuildingSearch.filters || [])
- const validOffshoreTopFilters = getValidFilters(offshoreTopSearch.filters || [])
- const validOffshoreHullFilters = getValidFilters(offshoreHullSearch.filters || [])
-
-
- // URL에서 아이템 타입 가져오기
- const itemType = params.type || "ship"
-
- return (
- <div>
- {itemType === "ship" && (
- <ItemsShipTable
- promises={Promise.all([
- getShipbuildingItems({
- ...shipbuildingSearch,
- filters: validShipbuildingFilters,
- }),
- ]).then(([result]) => result)}
- />
- )}
-
- {itemType === "top" && (
- <OffshoreTopTable
- promises={Promise.all([
- getOffshoreTopItems({
- ...offshoreTopSearch,
- filters: validOffshoreTopFilters,
- }),
- ]).then(([result]) => result)}
- />
- )}
-
- {itemType === "hull" && (
- <OffshoreHullTable
- promises={Promise.all([
- getOffshoreHullItems({
- ...offshoreHullSearch,
- filters: validOffshoreHullFilters,
- }),
- ]).then(([result]) => result)}
- />
- )}
- </div>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/items/page.tsx b/app/[lng]/procurement/(procurement)/items/page.tsx
deleted file mode 100644
index f8d9a5b1..00000000
--- a/app/[lng]/procurement/(procurement)/items/page.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-// app/items/page.tsx (업데이트)
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { searchParamsCache } from "@/lib/items/validations"
-import { getItems } from "@/lib/items/service"
-import { ItemsTable } from "@/lib/items/table/items-table"
-import { ViewModeToggle } from "@/components/data-table/view-mode-toggle"
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- // pageSize 기반으로 모드 자동 결정
- const isInfiniteMode = search.perPage >= 1_000_000
-
- // 페이지네이션 모드일 때만 서버에서 데이터 가져오기
- // 무한 스크롤 모드에서는 클라이언트에서 SWR로 데이터 로드
- const promises = isInfiniteMode
- ? undefined
- : Promise.all([
- getItems(search), // searchParamsCache의 결과를 그대로 사용
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 패키지 넘버
- </h2>
- {/* <p className="text-muted-foreground">
- S-EDP로부터 수신된 패키지 정보이며 PR 전 입찰, 견적에 사용되며 벤더 데이터, 문서와 연결됩니다.
- </p> */}
- </div>
- </div>
-
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* DateRangePicker 등 추가 컴포넌트 */}
- </React.Suspense>
-
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- {/* 통합된 ItemsTable 컴포넌트 사용 */}
- <ItemsTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/layout.tsx b/app/[lng]/procurement/(procurement)/layout.tsx
deleted file mode 100644
index 82b53307..00000000
--- a/app/[lng]/procurement/(procurement)/layout.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { ReactNode } from 'react';
-import { Header } from '@/components/layout/Header';
-import { SiteFooter } from '@/components/layout/Footer';
-
-export default function EvcpLayout({ children }: { children: ReactNode }) {
- return (
- <div className="relative flex min-h-svh flex-col bg-background">
- {/* <div className="relative flex min-h-svh flex-col bg-slate-100 "> */}
- <Header />
- <main className="flex flex-1 flex-col">
- <div className='container-wrapper'>
- {children}
- </div>
- </main>
- <SiteFooter/>
- </div>
- );
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/menu-list/page.tsx b/app/[lng]/procurement/(procurement)/menu-list/page.tsx
deleted file mode 100644
index dee45ab1..00000000
--- a/app/[lng]/procurement/(procurement)/menu-list/page.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-// app/evcp/menu-list/page.tsx
-
-import { Suspense } from "react";
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
-import { Button } from "@/components/ui/button";
-import { RefreshCw, Settings } from "lucide-react";
-import { getActiveUsers, getMenuAssignments } from "@/lib/menu-list/servcie";
-import { InitializeButton } from "@/lib/menu-list/table/initialize-button";
-import { MenuListTable } from "@/lib/menu-list/table/menu-list-table";
-import { Shell } from "@/components/shell"
-import * as React from "react"
-
-export default async function MenuListPage() {
- // 초기 데이터 로드
- const [menusResult, usersResult] = await Promise.all([
- getMenuAssignments(),
- getActiveUsers()
- ]);
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 메뉴 관리
- </h2>
- {/* <p className="text-muted-foreground">
- 각 메뉴별로 담당자를 지정하고 관리할 수 있습니다.
- </p> */}
- </div>
- </div>
-
- </div>
-
-
- <React.Suspense
- fallback={
- ""
- }
- >
- <Card>
- <CardHeader>
- <CardTitle className="flex items-center gap-2">
- <Settings className="h-5 w-5" />
- 메뉴 리스트
- </CardTitle>
- <CardDescription>
- 시스템의 모든 메뉴와 담당자 정보를 확인할 수 있습니다.
- {menusResult.data?.length > 0 && (
- <span className="ml-2 text-sm">
- 총 {menusResult.data.length}개의 메뉴
- </span>
- )}
- </CardDescription>
- </CardHeader>
- <CardContent>
- <Suspense fallback={<div className="text-center py-8">로딩 중...</div>}>
- <MenuListTable
- initialMenus={menusResult.data || []}
- initialUsers={usersResult.data || []}
- />
- </Suspense>
- </CardContent>
- </Card>
- </React.Suspense>
- </Shell>
-
- );
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/payment-conditions/page.tsx b/app/[lng]/procurement/(procurement)/payment-conditions/page.tsx
deleted file mode 100644
index d001a39d..00000000
--- a/app/[lng]/procurement/(procurement)/payment-conditions/page.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import * as React from "react";
-import { type SearchParams } from "@/types/table";
-import { getValidFilters } from "@/lib/data-table";
-import { Shell } from "@/components/shell";
-import { Skeleton } from "@/components/ui/skeleton";
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton";
-import { SearchParamsCache } from "@/lib/payment-terms/validations";
-import { getPaymentTerms } from "@/lib/payment-terms/service";
-import { PaymentTermsTable } from "@/lib/payment-terms/table/payment-terms-table";
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>;
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams;
- const search = SearchParamsCache.parse(searchParams);
- const validFilters = getValidFilters(search.filters);
-
- const promises = Promise.all([
- getPaymentTerms({
- ...search,
- filters: validFilters,
- }),
- ]);
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">지급 조건 관리</h2>
- {/* <p className="text-muted-foreground">
- 지급 조건(Payment Terms)을 등록, 수정, 삭제할 수 있습니다.
- </p> */}
- </div>
- </div>
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}></React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={4}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <PaymentTermsTable promises={promises} />
- </React.Suspense>
- </Shell>
- );
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/po-rfq/page.tsx b/app/[lng]/procurement/(procurement)/po-rfq/page.tsx
deleted file mode 100644
index 4a04d6a8..00000000
--- a/app/[lng]/procurement/(procurement)/po-rfq/page.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { getPORfqs } from "@/lib/procurement-rfqs/services"
-import { searchParamsCache } from "@/lib/procurement-rfqs/validations"
-import { getValidFilters } from "@/lib/data-table"
-import { Shell } from "@/components/shell"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { RFQListTable } from "@/lib/procurement-rfqs/table/rfq-table"
-import { type SearchParams } from "@/types/table"
-import * as React from "react"
-
-interface RfqPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqPage(props: RfqPageProps) {
- // searchParams를 await하여 resolve
- const searchParams = await props.searchParams
-
- // 파라미터 파싱
- const search = searchParamsCache.parse(searchParams);
- const validFilters = getValidFilters(search.filters);
-
- // RFQ 서버는 기본필터와 고급필터를 분리해서 받으므로 그대로 전달
- const promises = Promise.all([
- getPORfqs({
- ...search, // 모든 파라미터 전달 (page, perPage, sort, basicFilters, filters 등)
- filters: validFilters, // 고급 필터를 명시적으로 오버라이드 (파싱된 버전)
- })
- ])
-
- return (
- <Shell variant="fullscreen" className="h-full"> {/* fullscreen variant 사용 */}
- {/* 고정 헤더 영역 */}
- <div className="flex-shrink-0">
- <div className="flex items-center justify-between">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- RFQ
- </h2>
- </div>
- </div>
- </div>
-
- {/* 테이블 영역 - 남은 공간 모두 차지 */}
- <div className="flex-1 min-h-0">
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={8}
- searchableColumnCount={2}
- filterableColumnCount={3}
- cellWidths={["10rem", "15rem", "12rem", "12rem", "8rem", "8rem", "10rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <RFQListTable promises={promises} className="h-full" />
- </React.Suspense>
- </div>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/po/page.tsx b/app/[lng]/procurement/(procurement)/po/page.tsx
deleted file mode 100644
index b4dd914f..00000000
--- a/app/[lng]/procurement/(procurement)/po/page.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { getPOs } from "@/lib/po/service"
-import { searchParamsCache } from "@/lib/po/validations"
-import { PoListsTable } from "@/lib/po/table/po-table"
-
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getPOs({
- ...search,
- filters: validFilters,
- }),
- ])
-
- return (
- <Shell className="gap-2">
-
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- PO 확인 및 전자서명
- </h2>
- {/* <p className="text-muted-foreground">
- 기간계 시스템으로부터 PO를 확인하고 협력업체에게 전자서명을 요청할 수 있습니다. 요쳥된 전자서명의 이력 또한 확인할 수 있습니다.
-
- </p> */}
- </div>
- </div>
- </div>
-
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <PoListsTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/poa/page.tsx b/app/[lng]/procurement/(procurement)/poa/page.tsx
deleted file mode 100644
index 1c244991..00000000
--- a/app/[lng]/procurement/(procurement)/poa/page.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { getChangeOrders } from "@/lib/poa/service"
-import { searchParamsCache } from "@/lib/poa/validations"
-import { ChangeOrderListsTable } from "@/lib/poa/table/poa-table"
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getChangeOrders({
- ...search,
- filters: validFilters,
- }),
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 변경 PO 확인 및 전자서명
- </h2>
- {/* <p className="text-muted-foreground">
- 발행된 PO의 변경 내역을 확인하고 관리할 수 있습니다.
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <ChangeOrderListsTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/pq-criteria/[pqListId]/page.tsx b/app/[lng]/procurement/(procurement)/pq-criteria/[pqListId]/page.tsx
deleted file mode 100644
index 15cb3bf3..00000000
--- a/app/[lng]/procurement/(procurement)/pq-criteria/[pqListId]/page.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { searchParamsCache } from "@/lib/pq/validations"
-import { getPQsByListId } from "@/lib/pq/service"
-import { PqsTable } from "@/lib/pq/pq-criteria/pq-table"
-import { notFound } from "next/navigation"
-
-interface PQDetailPageProps {
- params: Promise<{ pqListId: string }>
- searchParams: Promise<SearchParams>
-}
-
-export default async function PQDetailPage(props: PQDetailPageProps) {
- const params = await props.params
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- const pqListId = parseInt(params.pqListId)
- if (isNaN(pqListId)) {
- notFound()
- }
-
- // filters가 없는 경우를 처리
- const validFilters = getValidFilters(search.filters)
-
- // PQ 항목들 가져오기
- const promises = Promise.all([
- getPQsByListId(pqListId, {
- ...search,
- filters: validFilters,
- })
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- PQ 항목 관리
- </h2>
- {/* <p className="text-muted-foreground">
- 선택한 PQ 목록의 세부 항목들을 관리할 수 있습니다.
- </p> */}
- </div>
- </div>
-
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={8}
- searchableColumnCount={1}
- filterableColumnCount={3}
- cellWidths={["10rem", "15rem", "20rem", "15rem", "10rem", "10rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <PqsTable
- promises={promises}
- pqListId={pqListId}
- />
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/pq-criteria/page.tsx b/app/[lng]/procurement/(procurement)/pq-criteria/page.tsx
deleted file mode 100644
index 1a337cc9..00000000
--- a/app/[lng]/procurement/(procurement)/pq-criteria/page.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { searchParamsCache } from "@/lib/pq/validations"
-import { getPQLists } from "@/lib/pq/service"
-import { PqListsTable } from "@/lib/pq/table/pq-lists-table"
-import { getProjects } from "@/lib/pq/service"
-
-interface ProjectPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function ProjectPage(props: ProjectPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- // filters가 없는 경우를 처리
- const validFilters = getValidFilters(search.filters)
-
- // // 프로젝트별 PQ 데이터 가져오기
- const promises = Promise.all([
- getPQLists({
- ...search,
- filters: validFilters,
- }),
- getProjects()
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- PQ 리스트 관리
- </h2>
- {/* <p className="text-muted-foreground">
- 협력업체 등록을 위한, 협력업체가 제출할 PQ 항목을: 프로젝트별로 관리할 수 있습니다.
- </p> */}
- </div>
- </div>
-
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <PqListsTable
- promises={promises}
- />
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/pq_new/[vendorId]/[submissionId]/page.tsx b/app/[lng]/procurement/(procurement)/pq_new/[vendorId]/[submissionId]/page.tsx
deleted file mode 100644
index b4b51363..00000000
--- a/app/[lng]/procurement/(procurement)/pq_new/[vendorId]/[submissionId]/page.tsx
+++ /dev/null
@@ -1,206 +0,0 @@
-import * as React from "react"
-import { Metadata } from "next"
-import Link from "next/link"
-import { notFound } from "next/navigation"
-import { ArrowLeft } from "lucide-react"
-import { Shell } from "@/components/shell"
-import { Button } from "@/components/ui/button"
-import { Badge } from "@/components/ui/badge"
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
-import { Separator } from "@/components/ui/separator"
-import { getPQById, getPQDataByVendorId } from "@/lib/pq/service"
-import { unstable_noStore as noStore } from 'next/cache'
-import { PQReviewWrapper } from "@/components/pq-input/pq-review-wrapper"
-import { formatDate } from "@/lib/utils"
-
-export const metadata: Metadata = {
- title: "PQ 검토",
- description: "협력업체의 Pre-Qualification 답변을 검토합니다.",
-}
-
-// 페이지가 기본적으로 동적임을 나타냄
-export const dynamic = "force-dynamic"
-
-interface PQReviewPageProps {
- params: Promise<{
- vendorId: string;
- submissionId: string;
- }>
-}
-
-export default async function PQReviewPage(props: PQReviewPageProps) {
- // 캐시 비활성화
- noStore()
-
- const params = await props.params
- const vendorId = parseInt(params.vendorId, 10)
- const submissionId = parseInt(params.submissionId, 10)
-
- try {
- // PQ Submission 정보 조회
- const pqSubmission = await getPQById(submissionId, vendorId)
-
- // PQ 데이터 조회 (질문과 답변)
- const pqData = await getPQDataByVendorId(vendorId, pqSubmission.projectId || undefined)
-
- // 프로젝트 정보 (프로젝트 PQ인 경우)
- const projectInfo = pqSubmission.projectId ? {
- id: pqSubmission.projectId,
- projectCode: pqSubmission.projectCode || '',
- projectName: pqSubmission.projectName || '',
- status: pqSubmission.status,
- submittedAt: pqSubmission.submittedAt,
- } : null
-
- // PQ 유형 및 상태 레이블
- const typeLabel = pqSubmission.type === "GENERAL" ? "일반 PQ" :
- pqSubmission.type === "PROJECT" ? "프로젝트 PQ" :
- pqSubmission.type === "NON_INSPECTION" ? "미실사 PQ" : "일반 PQ"
- const statusLabel = getStatusLabel(pqSubmission.status)
- const statusVariant = getStatusVariant(pqSubmission.status)
-
- // 수정 가능 여부 (SUBMITTED 상태일 때만 가능)
- const canReview = pqSubmission.status === "SUBMITTED"
-
- return (
- <Shell className="gap-6 max-w-5xl">
- <div className="flex items-center justify-between">
- <div className="flex items-center gap-4">
- <Button variant="outline" size="sm" asChild>
- <Link href="/procurement/pq_new">
- <ArrowLeft className="w-4 h-4 mr-2" />
- 목록으로
- </Link>
- </Button>
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- {pqSubmission.vendorName} - {typeLabel}
- </h2>
- <div className="flex items-center gap-2 mt-1">
- <Badge variant={statusVariant}>{statusLabel}</Badge>
- {projectInfo && (
- <span className="text-muted-foreground">
- {projectInfo.projectName} ({projectInfo.projectCode})
- </span>
- )}
- </div>
- </div>
- </div>
- </div>
-
- {/* 상태별 알림 */}
- {pqSubmission.status === "SUBMITTED" && (
- <Alert>
- <AlertTitle>제출 완료</AlertTitle>
- <AlertDescription>
- 협력업체가 {formatDate(pqSubmission.submittedAt, "kr")}에 PQ를 제출했습니다. 검토 후 승인 또는 거부할 수 있습니다.
- </AlertDescription>
- </Alert>
- )}
-
- {pqSubmission.status === "APPROVED" && (
- <Alert variant="success">
- <AlertTitle>승인됨</AlertTitle>
- <AlertDescription>
- {formatDate(pqSubmission.approvedAt, "kr")}에 승인되었습니다.
- </AlertDescription>
- </Alert>
- )}
-
- {pqSubmission.status === "REJECTED" && (
- <Alert variant="destructive">
- <AlertTitle>거부됨</AlertTitle>
- <AlertDescription>
- {formatDate(pqSubmission.rejectedAt, "kr")}에 거부되었습니다.
- {pqSubmission.rejectReason && (
- <div className="mt-2">
- <strong>사유:</strong> {pqSubmission.rejectReason}
- </div>
- )}
- </AlertDescription>
- </Alert>
- )}
-
- <Separator />
-
- {/* PQ 검토 컴포넌트 */}
- <Tabs defaultValue="review" className="w-full">
- <TabsList>
- <TabsTrigger value="review">PQ 검토</TabsTrigger>
- <TabsTrigger value="vendor-info">협력업체 정보</TabsTrigger>
- </TabsList>
-
- <TabsContent value="review" className="mt-4">
- <PQReviewWrapper
- pqData={pqData}
- vendorId={vendorId}
- pqSubmission={pqSubmission}
- canReview={canReview}
- />
- </TabsContent>
-
- <TabsContent value="vendor-info" className="mt-4">
- <div className="rounded-md border p-4">
- <h3 className="text-lg font-medium mb-4">협력업체 정보</h3>
- <div className="grid grid-cols-2 gap-4">
- <div>
- <p className="text-sm font-medium text-muted-foreground">업체명</p>
- <p>{pqSubmission.vendorName}</p>
- </div>
- <div>
- <p className="text-sm font-medium text-muted-foreground">업체 코드</p>
- <p>{pqSubmission.vendorCode}</p>
- </div>
- <div>
- <p className="text-sm font-medium text-muted-foreground">상태</p>
- <p>{pqSubmission.vendorStatus}</p>
- </div>
- {/* 필요시 추가 정보 표시 */}
- </div>
- </div>
- </TabsContent>
- </Tabs>
- </Shell>
- )
- } catch (error) {
- console.error("Error loading PQ:", error)
- notFound()
- }
-}
-
-// 상태 레이블 함수
-function getStatusLabel(status: string): string {
- switch (status) {
- case "REQUESTED":
- return "요청됨";
- case "IN_PROGRESS":
- return "진행 중";
- case "SUBMITTED":
- return "제출됨";
- case "APPROVED":
- return "승인됨";
- case "REJECTED":
- return "거부됨";
- default:
- return status;
- }
-}
-
-// 상태별 Badge 스타일
-function getStatusVariant(status: string): "default" | "outline" | "secondary" | "destructive" | "success" {
- switch (status) {
- case "REQUESTED":
- return "outline";
- case "IN_PROGRESS":
- return "secondary";
- case "SUBMITTED":
- return "default";
- case "APPROVED":
- return "success";
- case "REJECTED":
- return "destructive";
- default:
- return "outline";
- }
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/pq_new/page.tsx b/app/[lng]/procurement/(procurement)/pq_new/page.tsx
deleted file mode 100644
index 6a992ee5..00000000
--- a/app/[lng]/procurement/(procurement)/pq_new/page.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import * as React from "react"
-import { Metadata } from "next"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { Shell } from "@/components/shell"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { searchParamsPQReviewCache } from "@/lib/pq/validations"
-import { getPQSubmissions } from "@/lib/pq/service"
-import { PQSubmissionsTable } from "@/lib/pq/pq-review-table-new/vendors-table"
-import { InformationButton } from "@/components/information/information-button"
-export const metadata: Metadata = {
- title: "협력업체 PQ/실사 현황",
- description: "",
-}
-
-interface PQReviewPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function PQReviewPage(props: PQReviewPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsPQReviewCache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- // 디버깅 로그 추가
- console.log("=== PQ Page Debug ===");
- console.log("Raw searchParams:", searchParams);
- console.log("Raw basicFilters param:", searchParams.basicFilters);
- console.log("Raw pqBasicFilters param:", searchParams.pqBasicFilters);
- console.log("Parsed search:", search);
- console.log("search.filters:", search.filters);
- console.log("search.basicFilters:", search.basicFilters);
- console.log("search.pqBasicFilters:", search.pqBasicFilters);
- console.log("validFilters:", validFilters);
-
- // 기본 필터 처리 (통일된 이름 사용)
- let basicFilters = []
- if (search.basicFilters && search.basicFilters.length > 0) {
- basicFilters = search.basicFilters
- console.log("Using search.basicFilters:", basicFilters);
- } else if (search.pqBasicFilters && search.pqBasicFilters.length > 0) {
- // 하위 호환성을 위해 기존 이름도 지원
- basicFilters = search.pqBasicFilters
- console.log("Using search.pqBasicFilters:", basicFilters);
- } else {
- console.log("No basic filters found");
- }
-
- // 모든 필터를 합쳐서 처리
- const allFilters = [...validFilters, ...basicFilters]
-
- console.log("Final allFilters:", allFilters);
-
- // 조인 연산자도 통일된 이름 사용
- const joinOperator = search.basicJoinOperator || search.pqBasicJoinOperator || search.joinOperator || 'and';
- console.log("Final joinOperator:", joinOperator);
-
- // Promise.all로 감싸서 전달
- const promises = Promise.all([
- getPQSubmissions({
- ...search,
- filters: allFilters,
- joinOperator,
- })
- ])
-
- return (
- <Shell className="gap-4">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <div className="flex items-center gap-2">
- <h2 className="text-2xl font-bold tracking-tight">
- 협력업체 PQ/실사 현황
- </h2>
- <InformationButton pagePath="evcp/pq_new" />
- </div>
- </div>
- </div>
- </div>
-
- {/* Items처럼 직접 테이블 렌더링 */}
- <React.Suspense
- key={JSON.stringify(searchParams)} // URL 파라미터가 변경될 때마다 강제 리렌더링
- fallback={
- <DataTableSkeleton
- columnCount={8}
- searchableColumnCount={2}
- filterableColumnCount={3}
- cellWidths={["10rem", "15rem", "12rem", "12rem", "8rem", "8rem", "10rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <PQSubmissionsTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/project-gtc/page.tsx b/app/[lng]/procurement/(procurement)/project-gtc/page.tsx
deleted file mode 100644
index 554f17b0..00000000
--- a/app/[lng]/procurement/(procurement)/project-gtc/page.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { getProjectGtcList } from "@/lib/project-gtc/service"
-import { projectGtcSearchParamsSchema } from "@/lib/project-gtc/validations"
-import { ProjectGtcTable } from "@/lib/project-gtc/table/project-gtc-table"
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = projectGtcSearchParamsSchema.parse(searchParams)
-
- const promises = Promise.all([
- getProjectGtcList({
- page: search.page,
- perPage: search.perPage,
- search: search.search,
- sort: search.sort,
- }),
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- Project GTC 관리
- </h2>
- {/* <p className="text-muted-foreground">
- 프로젝트별 GTC(General Terms and Conditions) 파일을 관리할 수 있습니다.
- 각 프로젝트마다 하나의 GTC 파일을 업로드할 수 있으며, 파일 업로드 시 기존 파일은 자동으로 교체됩니다.
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* 추가 기능이 필요하면 여기에 추가 */}
- </React.Suspense>
-
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={8}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["3rem", "3rem", "12rem", "20rem", "10rem", "20rem", "15rem", "12rem", "3rem"]}
- shrinkZero
- />
- }
- >
- <ProjectGtcTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/project-vendors/page.tsx b/app/[lng]/procurement/(procurement)/project-vendors/page.tsx
deleted file mode 100644
index 525cff07..00000000
--- a/app/[lng]/procurement/(procurement)/project-vendors/page.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { ProjectAVLTable } from "@/lib/project-avl/table/proejctAVL-table"
-import { getProjecTAVL } from "@/lib/project-avl/service"
-import { searchProjectAVLParamsCache } from "@/lib/project-avl/validations"
-
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchProjectAVLParamsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getProjecTAVL({
- ...search,
- filters: validFilters,
- }),
-
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 프로젝트 AVL 리스트
- </h2>
- {/* <p className="text-muted-foreground">
- 프로젝트 PQ를 통과한 벤더의 리스트를 보여줍니다.{" "}
- <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span>
- 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다.
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <ProjectAVLTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/projects/page.tsx b/app/[lng]/procurement/(procurement)/projects/page.tsx
deleted file mode 100644
index 8c332c6c..00000000
--- a/app/[lng]/procurement/(procurement)/projects/page.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { ItemsTable } from "@/lib/items/table/items-table"
-import { getProjectLists } from "@/lib/projects/service"
-import { ProjectsTable } from "@/lib/projects/table/projects-table"
-import { searchParamsProjectsCache } from "@/lib/projects/validation"
-
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsProjectsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getProjectLists({
- ...search,
- filters: validFilters,
- }),
-
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 수행 프로젝트 리스트 from S-EDP
- </h2>
- {/* <p className="text-muted-foreground">
- S-EDP로부터 수신하는 프로젝트 리스트입니다. 향후 MDG로 전환됩니다.{" "}
- <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span>
- 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다.
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <ProjectsTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/report/page.tsx b/app/[lng]/procurement/(procurement)/report/page.tsx
deleted file mode 100644
index 2782c3ac..00000000
--- a/app/[lng]/procurement/(procurement)/report/page.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import * as React from "react";
-import { Skeleton } from "@/components/ui/skeleton";
-import { Shell } from "@/components/shell";
-import { ErrorBoundary } from "@/components/error-boundary";
-import { getDashboardData } from "@/lib/dashboard/service";
-import { DashboardClient } from "@/lib/dashboard/dashboard-client";
-
-// 데이터 fetch 시 비동기 함수 호출 후 await 하므로 static-pre-render 과정에서 dynamic-server-error 발생.
-// 따라서, dynamic 속성을 force-dynamic 으로 설정하여 동적 렌더링 처리
-// getDashboardData 함수에 대한 Promise를 넘기는 식으로 수정하게 되면 force-dynamic 선언을 제거해도 됨.
-export const dynamic = 'force-dynamic'
-
-export default async function IndexPage() {
- // domain을 명시적으로 전달
- const domain = "procurement";
-
- try {
- // 서버에서 직접 데이터 fetch
- const dashboardData = await getDashboardData(domain);
-
- return (
- <Shell className="gap-2">
- <DashboardClient initialData={dashboardData} />
- </Shell>
- );
- } catch (error) {
- console.error("Dashboard data fetch error:", error);
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-center py-12">
- <div className="text-center space-y-2">
- <p className="text-destructive">데이터를 불러오는데 실패했습니다.</p>
- <p className="text-muted-foregroucdnd text-sm">{error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다."}</p>
- </div>
- </div>
- </Shell>
- );
- }
-}
-
-function DashboardSkeleton() {
- return (
- <div className="space-y-6">
- {/* 헤더 스켈레톤 */}
- <div className="flex items-center justify-between">
- <div className="space-y-2">
- <Skeleton className="h-8 w-48" />
- <Skeleton className="h-4 w-72" />
- </div>
- <Skeleton className="h-10 w-24" />
- </div>
-
- {/* 요약 카드 스켈레톤 */}
- <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
- {[...Array(4)].map((_, i) => (
- <div key={i} className="space-y-3 p-6 border rounded-lg">
- <div className="flex items-center justify-between">
- <Skeleton className="h-4 w-16" />
- <Skeleton className="h-4 w-4" />
- </div>
- <Skeleton className="h-8 w-12" />
- <Skeleton className="h-3 w-20" />
- </div>
- ))}
- </div>
-
- {/* 차트 스켈레톤 */}
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
- {[...Array(2)].map((_, i) => (
- <div key={i} className="space-y-4 p-6 border rounded-lg">
- <div className="space-y-2">
- <Skeleton className="h-6 w-32" />
- <Skeleton className="h-4 w-48" />
- </div>
- <Skeleton className="h-[300px] w-full" />
- </div>
- ))}
- </div>
-
- {/* 탭 스켈레톤 */}
- <div className="space-y-4">
- <Skeleton className="h-10 w-64" />
- <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
- {[...Array(6)].map((_, i) => (
- <div key={i} className="space-y-4 p-6 border rounded-lg">
- <Skeleton className="h-6 w-32" />
- <div className="space-y-3">
- <div className="flex justify-between">
- <Skeleton className="h-4 w-16" />
- <Skeleton className="h-4 w-12" />
- </div>
- <div className="flex gap-2">
- <Skeleton className="h-6 w-16" />
- <Skeleton className="h-6 w-16" />
- <Skeleton className="h-6 w-16" />
- </div>
- <Skeleton className="h-2 w-full" />
- </div>
- </div>
- ))}
- </div>
- </div>
- </div>
- );
-}
diff --git a/app/[lng]/procurement/(procurement)/rfq/[id]/cbe/page.tsx b/app/[lng]/procurement/(procurement)/rfq/[id]/cbe/page.tsx
deleted file mode 100644
index fb288a98..00000000
--- a/app/[lng]/procurement/(procurement)/rfq/[id]/cbe/page.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { searchParamsCBECache } from "@/lib/rfqs/validations"
-import { getCBE } from "@/lib/rfqs/service"
-import { CbeTable } from "@/lib/rfqs/cbe-table/cbe-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqCBEPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsCBECache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getCBE({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
-
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- Commercial Bid Evaluation
- </h3>
- <p className="text-sm text-muted-foreground">
- 초대된 협력업체에게 CBE를 보낼 수 있습니다. <br />"발행하기" 버튼을 통해 CBE를 전송하면 CBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p>
- </div>
- <Separator />
- <div>
- <CbeTable promises={promises} rfqId={idAsNumber} />
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/rfq/[id]/layout.tsx b/app/[lng]/procurement/(procurement)/rfq/[id]/layout.tsx
deleted file mode 100644
index 92817b4b..00000000
--- a/app/[lng]/procurement/(procurement)/rfq/[id]/layout.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import { Metadata } from "next"
-import Link from "next/link"
-import { Separator } from "@/components/ui/separator"
-import { SidebarNav } from "@/components/layout/sidebar-nav"
-import { RfqViewWithItems } from "@/db/schema/rfq"
-import { findRfqById } from "@/lib/rfqs/service"
-import { formatDate } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
-import { ArrowLeft } from "lucide-react"
-
-export const metadata: Metadata = {
- title: "Vendor Detail",
-}
-
-export default async function RfqLayout({
- children,
- params,
-}: {
- children: React.ReactNode
- params: { lng: string, id: string }
-}) {
-
- // 1) URL 파라미터에서 id 추출, Number로 변환
- const resolvedParams = await params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
- // 2) DB에서 해당 협력업체 정보 조회
- const rfq: RfqViewWithItems | null = await findRfqById(idAsNumber)
-
- // 3) 사이드바 메뉴
- const sidebarNavItems = [
- {
- title: "Matched Vendors",
- href: `/${lng}/evcp/rfq/${id}`,
- },
- {
- title: "TBE",
- href: `/${lng}/evcp/rfq/${id}/tbe`,
- },
- {
- title: "CBE",
- href: `/${lng}/evcp/rfq/${id}/cbe`,
- },
-
- ]
-
- return (
- <>
- <div className="container py-6">
- <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
- <div className="hidden space-y-6 p-10 pb-16 md:block">
- <div className="flex items-center justify-end mb-4">
- <Link href={`/${lng}/evcp/rfq`} passHref>
- <Button variant="ghost" className="flex items-center text-primary hover:text-primary/80 transition-colors p-0 h-auto">
- <ArrowLeft className="mr-1 h-4 w-4" />
- <span>RFQ 목록으로 돌아가기</span>
- </Button>
- </Link>
- </div>
- <div className="space-y-0.5">
- {/* 4) 협력업체 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */}
- <h2 className="text-2xl font-bold tracking-tight">
- {rfq
- ? `${rfq.projectCode ?? ""} ${rfq.rfqCode ?? ""} 관리`
- : "Loading RFQ..."}
- </h2>
-
- <p className="text-muted-foreground">
- {rfq
- ? `${rfq.description ?? ""} ${rfq.lines.map(line => line.itemCode).join(", ")}`
- : ""}
- </p>
- <h3>Due Date:{rfq && rfq?.dueDate && <strong>{formatDate(rfq?.dueDate, "KR")}</strong>}</h3>
- </div>
- <Separator className="my-6" />
- <div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
- <aside className="lg:w-64 flex-shrink-0">
- <SidebarNav items={sidebarNavItems} />
- </aside>
- <div className="lg:w-[calc(100%-16rem)] overflow-auto">{children}</div>
- </div>
- </div>
- </section>
- </div>
- </>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/rfq/[id]/page.tsx b/app/[lng]/procurement/(procurement)/rfq/[id]/page.tsx
deleted file mode 100644
index 1a9f4b18..00000000
--- a/app/[lng]/procurement/(procurement)/rfq/[id]/page.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { getMatchedVendors } from "@/lib/rfqs/service"
-import { searchParamsMatchedVCache } from "@/lib/rfqs/validations"
-import { MatchedVendorsTable } from "@/lib/rfqs/vendor-table/vendors-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsMatchedVCache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getMatchedVendors({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
-
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- Vendors
- </h3>
- <p className="text-sm text-muted-foreground">
- 등록된 협력업체 중에서 이 RFQ 아이템에 매칭되는 업체를 보여줍니다. <br/>"발행하기" 버튼을 통해 RFQ를 전송하면 첨부파일과 함께 RFQ 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p>
- </div>
- <Separator />
- <div>
- <MatchedVendorsTable promises={promises} rfqId={idAsNumber}/>
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/rfq/[id]/tbe/page.tsx b/app/[lng]/procurement/(procurement)/rfq/[id]/tbe/page.tsx
deleted file mode 100644
index 76eea302..00000000
--- a/app/[lng]/procurement/(procurement)/rfq/[id]/tbe/page.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { getTBE } from "@/lib/rfqs/service"
-import { searchParamsTBECache } from "@/lib/rfqs/validations"
-import { TbeTable } from "@/lib/rfqs/tbe-table/tbe-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqTBEPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsTBECache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getTBE({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
-
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- Technical Bid Evaluation
- </h3>
- <p className="text-sm text-muted-foreground">
- 초대된 협력업체에게 TBE를 보낼 수 있습니다. <br/>"발행하기" 버튼을 통해 TBE를 전송하면 첨부파일과 함께 TBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p>
- </div>
- <Separator />
- <div>
- <TbeTable promises={promises} rfqId={idAsNumber}/>
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/rfq/page.tsx b/app/[lng]/procurement/(procurement)/rfq/page.tsx
deleted file mode 100644
index 26f49cfb..00000000
--- a/app/[lng]/procurement/(procurement)/rfq/page.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-
-import { searchParamsCache } from "@/lib/rfqs/validations"
-import { getRfqs, getRfqStatusCounts } from "@/lib/rfqs/service"
-import { RfqsTable } from "@/lib/rfqs/table/rfqs-table"
-import { getAllItems } from "@/lib/items/service"
-import { RfqType } from "@/lib/rfqs/validations"
-
-interface RfqPageProps {
- searchParams: Promise<SearchParams>;
- rfqType: RfqType;
- title: string;
- description: string;
-}
-
-export default async function RfqPage({
- searchParams,
- rfqType = RfqType.PURCHASE,
- title = "RFQ",
- description = "RFQ를 등록하고 관리할 수 있습니다."
-}: RfqPageProps) {
- const search = searchParamsCache.parse(await searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getRfqs({
- ...search,
- filters: validFilters,
- rfqType // 전달받은 rfqType 사용
- }),
- getRfqStatusCounts(rfqType), // rfqType 전달
- getAllItems()
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- {title}
- </h2>
- {/* <p className="text-muted-foreground">
- {description}
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <RfqsTable promises={promises} rfqType={rfqType} />
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/settings/layout.tsx b/app/[lng]/procurement/(procurement)/settings/layout.tsx
deleted file mode 100644
index 6c380919..00000000
--- a/app/[lng]/procurement/(procurement)/settings/layout.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Metadata } from "next"
-
-import { Separator } from "@/components/ui/separator"
-import { SidebarNav } from "@/components/layout/sidebar-nav"
-
-export const metadata: Metadata = {
- title: "Settings",
- // description: "Advanced form example using react-hook-form and Zod.",
-}
-
-
-interface SettingsLayoutProps {
- children: React.ReactNode
- params: { lng: string }
-}
-
-export default async function SettingsLayout({
- children,
- params,
-}: {
- children: React.ReactNode
- params: { lng: string }
-}) {
- const resolvedParams = await params
- const lng = resolvedParams.lng
-
-
- const sidebarNavItems = [
-
- {
- title: "Account",
- href: `/${lng}/evcp/settings`,
- },
- {
- title: "Preferences",
- href: `/${lng}/evcp/settings/preferences`,
- }
-
-
- ]
-
-
- return (
- <>
- <div className="container py-6">
- <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
- <div className="hidden space-y-6 p-10 pb-16 md:block">
- <div className="space-y-0.5">
- <h2 className="text-2xl font-bold tracking-tight">설정</h2>
- {/* <p className="text-muted-foreground">
- Manage your account settings and preferences.
- </p> */}
- </div>
- <Separator className="my-6" />
- <div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
- <aside className="-mx-4 lg:w-1/5">
- <SidebarNav items={sidebarNavItems} />
- </aside>
- <div className="flex-1 ">{children}</div>
- </div>
- </div>
- </section>
- </div>
-
-
- </>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/settings/page.tsx b/app/[lng]/procurement/(procurement)/settings/page.tsx
deleted file mode 100644
index eba5e948..00000000
--- a/app/[lng]/procurement/(procurement)/settings/page.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { AccountForm } from "@/components/settings/account-form"
-
-export default function SettingsAccountPage() {
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">Account</h3>
- {/* <p className="text-sm text-muted-foreground">
- Update your account settings. Set your preferred language and
- timezone.
- </p> */}
- </div>
- <Separator />
- <AccountForm />
- </div>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/settings/preferences/page.tsx b/app/[lng]/procurement/(procurement)/settings/preferences/page.tsx
deleted file mode 100644
index e2a88021..00000000
--- a/app/[lng]/procurement/(procurement)/settings/preferences/page.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { AppearanceForm } from "@/components/settings/appearance-form"
-
-export default function SettingsAppearancePage() {
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">Preference</h3>
- <p className="text-sm text-muted-foreground">
- Customize the preference of the app.
- </p>
- </div>
- <Separator />
- <AppearanceForm />
- </div>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/system/admin-users/page.tsx b/app/[lng]/procurement/(procurement)/system/admin-users/page.tsx
deleted file mode 100644
index 11a9e9fb..00000000
--- a/app/[lng]/procurement/(procurement)/system/admin-users/page.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { DateRangePicker } from "@/components/date-range-picker"
-import { Separator } from "@/components/ui/separator"
-
-import { searchParamsCache } from "@/lib/admin-users/validations"
-import { getAllCompanies, getAllRoles, getUserCountGroupByCompany, getUserCountGroupByRole, getUsers } from "@/lib/admin-users/service"
-import { AdmUserTable } from "@/lib/admin-users/table/ausers-table"
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function UserTable(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getUsers({
- ...search,
- filters: validFilters,
- }),
- getUserCountGroupByCompany(),
- getUserCountGroupByRole(),
- getAllCompanies(),
- getAllRoles()
- ])
-
- return (
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">Vendor Admin User Management</h3>
- <p className="text-sm text-muted-foreground">
- 협력업체의 유저 전체를 조회하고 어드민 유저를 생성할 수 있는 페이지입니다. 이곳에서 초기 유저를 생성시킬 수 있습니다. <br />생성 후에는 생성된 사용자의 이메일로 생성 통보 이메일이 발송되며 사용자는 이메일과 OTP로 로그인이 가능합니다.
- </p>
- </div>
- <Separator />
- <AdmUserTable promises={promises} />
- </div>
- </React.Suspense>
-
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/system/layout.tsx b/app/[lng]/procurement/(procurement)/system/layout.tsx
deleted file mode 100644
index 2776ed8b..00000000
--- a/app/[lng]/procurement/(procurement)/system/layout.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { Metadata } from "next"
-
-import { Separator } from "@/components/ui/separator"
-import { SidebarNav } from "@/components/layout/sidebar-nav"
-
-export const metadata: Metadata = {
- title: "System Setting",
- // description: "Advanced form example using react-hook-form and Zod.",
-}
-
-
-interface SettingsLayoutProps {
- children: React.ReactNode
- params: { lng: string }
-}
-
-export default async function SettingsLayout({
- children,
- params,
-}: {
- children: React.ReactNode
- params: { lng: string }
-}) {
- const resolvedParams = await params
- const lng = resolvedParams.lng
-
-
- const sidebarNavItems = [
-
- {
- title: "삼성중공업 사용자",
- href: `/${lng}/evcp/system`,
- },
- {
- title: "Roles",
- href: `/${lng}/evcp/system/roles`,
- },
- {
- title: "권한 통제",
- href: `/${lng}/evcp/system/permissions`,
- },
- {
- title: "협력업체 사용자",
- href: `/${lng}/evcp/system/admin-users`,
- },
-
- {
- title: "비밀번호 정책",
- href: `/${lng}/evcp/system/password-policy`,
- },
-
- ]
-
-
- return (
- <>
- <div className="container py-6">
- <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
- <div className="hidden space-y-6 p-10 pb-16 md:block">
- <div className="space-y-0.5">
- <h2 className="text-2xl font-bold tracking-tight">시스템 설정</h2>
- {/* <p className="text-muted-foreground">
- 사용자, 롤, 접근 권한을 관리하세요.
- </p> */}
- </div>
- <Separator className="my-6" />
- <div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
- <aside className="-mx-4 lg:w-1/5">
- <SidebarNav items={sidebarNavItems} />
- </aside>
- <div className="flex-1 ">{children}</div>
- </div>
- </div>
- </section>
- </div>
-
-
- </>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/system/page.tsx b/app/[lng]/procurement/(procurement)/system/page.tsx
deleted file mode 100644
index fe0a262c..00000000
--- a/app/[lng]/procurement/(procurement)/system/page.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import * as React from "react"
-import { getValidFilters } from "@/lib/data-table"
-import { searchParamsCache } from "@/lib/admin-users/validations"
-import { getAllRoles, getUsersEVCP } from "@/lib/users/service"
-import { getUserCountGroupByRole } from "@/lib/admin-users/service"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { UserTable } from "@/lib/users/table/users-table"
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function SystemUserPage(props: IndexPageProps) {
-
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getUsersEVCP({
- ...search,
- filters: validFilters,
- }),
- getUserCountGroupByRole(),
- getAllRoles()
- ])
-
- return (
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "12rem", "12rem", "12rem"]}
- shrinkZero
- />
- }
- >
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">SHI Users</h3>
- <p className="text-sm text-muted-foreground">
- 시스템 전체 사용자들을 조회하고 관리할 수 있는 페이지입니다. 사용자에게 롤을 할당하는 것으로 메뉴별 권한을 관리할 수 있습니다.
- </p>
- </div>
- <Separator />
- <UserTable promises={promises} />
- </div>
- </React.Suspense>
-
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/system/password-policy/page.tsx b/app/[lng]/procurement/(procurement)/system/password-policy/page.tsx
deleted file mode 100644
index 0f14fefe..00000000
--- a/app/[lng]/procurement/(procurement)/system/password-policy/page.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-// app/admin/password-policy/page.tsx
-
-import * as React from "react"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Separator } from "@/components/ui/separator"
-import { Alert, AlertDescription } from "@/components/ui/alert"
-import { AlertTriangle } from "lucide-react"
-import SecuritySettingsTable from "@/components/system/passwordPolicy"
-import { getSecuritySettings } from "@/lib/password-policy/service"
-
-
-export default async function PasswordPolicyPage() {
- try {
- // 보안 설정 데이터 로드
- const securitySettings = await getSecuritySettings()
-
- return (
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={4}
- searchableColumnCount={0}
- filterableColumnCount={0}
- cellWidths={["20rem", "30rem", "15rem", "10rem"]}
- shrinkZero
- />
- }
- >
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">협력업체 사용자 비밀번호 정책 설정</h3>
- <p className="text-sm text-muted-foreground">
- 협력업체 사용자들을 위한 비밀번호 정책과 보안 설정을 관리할 수 있습니다.
- </p>
- </div>
- <Separator />
- <SecuritySettingsTable initialSettings={securitySettings} />
- </div>
- </React.Suspense>
- )
- } catch (error) {
- console.error('Failed to load security settings:', error)
-
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">협력업체 사용자 비밀번호 정책 설정</h3>
- <p className="text-sm text-muted-foreground">
- 협력업체 사용자들을 위한 비밀번호 정책과 보안 설정을 관리할 수 있습니다.
- </p>
- </div>
- <Separator />
- <Alert variant="destructive">
- <AlertTriangle className="h-4 w-4" />
- <AlertDescription>
- 보안 설정을 불러오는 중 오류가 발생했습니다. 페이지를 새로고침하거나 관리자에게 문의하세요.
- </AlertDescription>
- </Alert>
- </div>
- )
- }
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/system/permissions/page.tsx b/app/[lng]/procurement/(procurement)/system/permissions/page.tsx
deleted file mode 100644
index 6aa2b693..00000000
--- a/app/[lng]/procurement/(procurement)/system/permissions/page.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import PermissionsTree from "@/components/system/permissionsTree"
-import { Separator } from "@/components/ui/separator"
-
-export default function PermissionsPage() {
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">Permissions</h3>
- <p className="text-sm text-muted-foreground">
- Set permissions to the menu by Role
- </p>
- </div>
- <Separator />
- <PermissionsTree/>
- </div>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/system/roles/page.tsx b/app/[lng]/procurement/(procurement)/system/roles/page.tsx
deleted file mode 100644
index fe074600..00000000
--- a/app/[lng]/procurement/(procurement)/system/roles/page.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Separator } from "@/components/ui/separator"
-
-import { searchParamsCache } from "@/lib/roles/validations"
-import { searchParamsCache as searchParamsCache2 } from "@/lib/admin-users/validations"
-import { RolesTable } from "@/lib/roles/table/roles-table"
-import { getRolesWithCount } from "@/lib/roles/services"
-import { getUsersAll } from "@/lib/users/service"
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function UserTable(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
- const search2 = searchParamsCache2.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getRolesWithCount({
- ...search,
- filters: validFilters,
- }),
-
-
- ])
-
-
- const promises2 = Promise.all([
- getUsersAll({
- ...search2,
- filters: validFilters,
- }, "evcp"),
- ])
-
-
- return (
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">Role Management</h3>
- <p className="text-sm text-muted-foreground">
- 역할을 생성하고 역할에 유저를 할당할 수 있는 페이지입니다. 역할에 메뉴의 접근 권한 역시 할당할 수 있습니다.
- </p>
- </div>
- <Separator />
- <RolesTable promises={promises} promises2={promises2} />
- </div>
- </React.Suspense>
-
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/tbe/page.tsx b/app/[lng]/procurement/(procurement)/tbe/page.tsx
deleted file mode 100644
index 1a7fdf86..00000000
--- a/app/[lng]/procurement/(procurement)/tbe/page.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { getAllTBE } from "@/lib/rfqs/service"
-import { searchParamsTBECache } from "@/lib/rfqs/validations"
-import { AllTbeTable } from "@/lib/tbe/table/tbe-table"
-import { RfqType } from "@/lib/rfqs/validations"
-import * as React from "react"
-import { Shell } from "@/components/shell"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
-
-interface IndexPageProps {
- params: {
- lng: string
- }
- searchParams: Promise<SearchParams>
-}
-
-// 타입별 페이지 설명 구성 (Budgetary 제외)
-const typeConfig: Record<string, { title: string; description: string; rfqType: RfqType }> = {
- "purchase": {
- title: "Purchase RFQ Technical Bid Evaluation",
- description: "실제 구매 발주 전 가격 요청을 위한 TBE입니다.",
- rfqType: RfqType.PURCHASE
- },
- "purchase-budgetary": {
- title: "Purchase Budgetary RFQ Technical Bid Evaluation",
- description: "프로젝트 수주 후, 공식 입찰 전 예산 책정을 위한 TBE입니다.",
- rfqType: RfqType.PURCHASE_BUDGETARY
- }
-}
-
-export default async function RfqTBEPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
-
- // URL 쿼리 파라미터에서 타입 추출
- const searchParams = await props.searchParams
- // 기본값으로 'purchase' 사용
- const typeParam = searchParams?.type as string || 'purchase'
-
- // 유효한 타입인지 확인하고 기본값 설정
- const validType = Object.keys(typeConfig).includes(typeParam) ? typeParam : 'purchase'
- const rfqType = typeConfig[validType].rfqType
-
- // SearchParams 파싱 (Zod)
- const search = searchParamsTBECache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- // 현재 선택된 타입의 데이터 로드
- const promises = Promise.all([
- getAllTBE({
- ...search,
- filters: validFilters,
- rfqType
- })
- ])
-
- // 페이지 경로 생성 함수 - 단순화
- const getTabUrl = (type: string) => {
- return `/${lng}/evcp/tbe?type=${type}`;
- }
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- Technical Bid Evaluation
- </h2>
- <p className="text-muted-foreground">
- 초대된 협력업체에게 TBE를 보낼 수 있습니다. <br/>
- 체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
- </p>
- </div>
- </div>
- </div>
-
- {/* 타입 선택 탭 (Budgetary 제외) */}
- <Tabs defaultValue={validType} value={validType} className="w-full">
- <TabsList className="grid grid-cols-2 w-full max-w-md">
- <TabsTrigger value="purchase" asChild>
- <a href={getTabUrl('purchase')}>Purchase</a>
- </TabsTrigger>
- <TabsTrigger value="purchase-budgetary" asChild>
- <a href={getTabUrl('purchase-budgetary')}>Purchase Budgetary</a>
- </TabsTrigger>
- </TabsList>
-
- <div className="mt-2">
- <p className="text-sm text-muted-foreground">
- {typeConfig[validType].description}
- </p>
- </div>
- </Tabs>
-
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <AllTbeTable promises={promises}/>
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/vendor-candidates/page.tsx b/app/[lng]/procurement/(procurement)/vendor-candidates/page.tsx
deleted file mode 100644
index fb80cf64..00000000
--- a/app/[lng]/procurement/(procurement)/vendor-candidates/page.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-
-import { getVendorCandidateCounts, getVendorCandidates } from "@/lib/vendor-candidates/service"
-import { searchParamsCandidateCache } from "@/lib/vendor-candidates/validations"
-import { VendorCandidateTable } from "@/lib/vendor-candidates/table/candidates-table"
-import { DateRangePicker } from "@/components/date-range-picker"
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCandidateCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getVendorCandidates({
- ...search,
- filters: validFilters,
- }),
- getVendorCandidateCounts()
- ])
-
- return (
- <Shell className="gap-2">
-
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 발굴업체 등록 관리
- </h2>
- {/* <p className="text-muted-foreground">
- 수집한 협력업체 후보를 등록하고 초대 메일을 송부할 수 있습니다.
- </p> */}
- </div>
- </div>
- </div>
-
- {/* 수집일 라벨과 DateRangePicker를 함께 배치 */}
- <div className="flex items-center justify-start gap-2">
- {/* <span className="text-sm font-medium">수집일 기간 설정: </span> */}
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- <DateRangePicker
- triggerSize="sm"
- triggerClassName="w-56 sm:w-60"
- align="end"
- shallow={false}
- showClearButton={true}
- placeholder="수집일 날짜 범위를 고르세요"
- />
- </React.Suspense>
- </div>
-
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <VendorCandidateTable promises={promises}/>
- </React.Suspense>
- </Shell>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/vendor-check-list/page.tsx b/app/[lng]/procurement/(procurement)/vendor-check-list/page.tsx
deleted file mode 100644
index e6f9ce82..00000000
--- a/app/[lng]/procurement/(procurement)/vendor-check-list/page.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { getGenralEvaluationsSchema } from "@/lib/general-check-list/validation"
-import { GeneralEvaluationsTable } from "@/lib/general-check-list/table/general-check-list-table"
-import { getGeneralEvaluations } from "@/lib/general-check-list/service"
-
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = getGenralEvaluationsSchema.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getGeneralEvaluations({
- ...search,
- filters: validFilters,
- }),
-
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 협력업체 평가자료 문항 관리
- </h2>
- {/* <p className="text-muted-foreground">
- 협력업체 평가에 사용되는 정기평가 체크리스트를 관리{" "}
- <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span>
- 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다.
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <GeneralEvaluationsTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/vendor-investigation/page.tsx b/app/[lng]/procurement/(procurement)/vendor-investigation/page.tsx
deleted file mode 100644
index af9f3e11..00000000
--- a/app/[lng]/procurement/(procurement)/vendor-investigation/page.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-
-import { VendorsInvestigationTable } from "@/lib/vendor-investigation/table/investigation-table"
-import { getVendorsInvestigation } from "@/lib/vendor-investigation/service"
-import { searchParamsInvestigationCache } from "@/lib/vendor-investigation/validations"
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsInvestigationCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getVendorsInvestigation({
- ...search,
- filters: validFilters,
- }),
- ])
-
- return (
- <Shell className="gap-2">
-
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 협력업체 실사 관리
- </h2>
- {/* <p className="text-muted-foreground">
- 요청된 Vendor 실사에 대한 스케줄 정보를 관리하고 결과를 입력할 수 있습니다.
-
- </p> */}
- </div>
- </div>
- </div>
-
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <VendorsInvestigationTable promises={promises}/>
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/vendor-type/page.tsx b/app/[lng]/procurement/(procurement)/vendor-type/page.tsx
deleted file mode 100644
index 96169e8a..00000000
--- a/app/[lng]/procurement/(procurement)/vendor-type/page.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-import { searchParamsCache } from "@/lib/vendor-type/validations"
-import { VendorTypesTable } from "@/lib/vendor-type/table/vendorTypes-table"
-import { getVendorTypes } from "@/lib/vendor-type/service"
-
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getVendorTypes({
- ...search,
- filters: validFilters,
- }),
-
- ])
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 업체 유형
- </h2>
- {/* <p className="text-muted-foreground">
- 업체 유형을 등록하고 관리할 수 있습니다.{" "}
-
- </p> */}
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <VendorTypesTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/vendors/[id]/info/items/page.tsx b/app/[lng]/procurement/(procurement)/vendors/[id]/info/items/page.tsx
deleted file mode 100644
index 5d5838c6..00000000
--- a/app/[lng]/procurement/(procurement)/vendors/[id]/info/items/page.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { getVendorItems } from "@/lib/vendors/service"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { searchParamsItemCache } from "@/lib/vendors/validations"
-import { VendorItemsTable } from "@/lib/vendors/items-table/item-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function SettingsAccountPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsItemCache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
-
-
- const promises = Promise.all([
- getVendorItems({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- 공급품목(패키지)
- </h3>
- <p className="text-sm text-muted-foreground">
- {/* 딜리버리가 가능한 아이템 리스트를 확인할 수 있습니다. */}
- </p>
- </div>
- <Separator />
- <div>
- <VendorItemsTable promises={promises} vendorId={idAsNumber}/>
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/vendors/[id]/info/layout.tsx b/app/[lng]/procurement/(procurement)/vendors/[id]/info/layout.tsx
deleted file mode 100644
index 7e2cd4f6..00000000
--- a/app/[lng]/procurement/(procurement)/vendors/[id]/info/layout.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import { Metadata } from "next"
-
-import { Separator } from "@/components/ui/separator"
-import { SidebarNav } from "@/components/layout/sidebar-nav"
-import { findVendorById } from "@/lib/vendors/service" // 가정: 여기에 findVendorById가 있다고 가정
-import { Vendor } from "@/db/schema/vendors"
-import { Button } from "@/components/ui/button"
-import { ArrowLeft } from "lucide-react"
-import Link from "next/link"
-export const metadata: Metadata = {
- title: "Vendor Detail",
-}
-
-export default async function SettingsLayout({
- children,
- params,
-}: {
- children: React.ReactNode
- params: { lng: string , id: string}
-}) {
-
- // 1) URL 파라미터에서 id 추출, Number로 변환
- const resolvedParams = await params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
- // 2) DB에서 해당 협력업체 정보 조회
- const vendor: Vendor | null = await findVendorById(idAsNumber)
-
- // 3) 사이드바 메뉴
- const sidebarNavItems = [
- {
- title: "연락처",
- href: `/${lng}/evcp/vendors/${id}/info`,
- },
- {
- title: "공급품목(패키지)",
- href: `/${lng}/evcp/vendors/${id}/info/items`,
- },
- {
- title: "공급품목(자재그룹)",
- href: `/${lng}/evcp/vendors/${id}/info/materials`,
- },
- {
- title: "견적 히스토리",
- href: `/${lng}/evcp/vendors/${id}/info/rfq-history`,
- },
- {
- title: "입찰 히스토리",
- href: `/${lng}/evcp/vendors/${id}/info/bid-history`,
- },
- {
- title: "계약 히스토리",
- href: `/${lng}/evcp/vendors/${id}/info/contract-history`,
- },
- ]
-
- return (
- <>
- <div className="container py-6">
- <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
- <div className="hidden space-y-6 p-10 pb-16 md:block">
- {/* RFQ 목록으로 돌아가는 링크 추가 */}
- <div className="flex items-center justify-end mb-4">
- <Link href={`/${lng}/evcp/vendors`} passHref>
- <Button variant="ghost" className="flex items-center text-primary hover:text-primary/80 transition-colors p-0 h-auto">
- <ArrowLeft className="mr-1 h-4 w-4" />
- <span>협력업체 목록으로 돌아가기</span>
- </Button>
- </Link>
- </div>
- <div className="space-y-0.5">
- {/* 4) 협력업체 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */}
- <h2 className="text-2xl font-bold tracking-tight">
- {vendor
- ? `${vendor.vendorCode ?? ""} - ${vendor.vendorName} 상세 정보`
- : "Loading Vendor..."}
- </h2>
- <p className="text-muted-foreground">협력업체 관련 상세사항을 확인하세요.</p>
- </div>
- <Separator className="my-6" />
- <div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
- <aside className="-mx-4 lg:w-1/5">
- <SidebarNav items={sidebarNavItems} />
- </aside>
- <div className="flex-1">{children}</div>
- </div>
- </div>
- </section>
- </div>
- </>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/vendors/[id]/info/materials/page.tsx b/app/[lng]/procurement/(procurement)/vendors/[id]/info/materials/page.tsx
deleted file mode 100644
index 0ebb66ba..00000000
--- a/app/[lng]/procurement/(procurement)/vendors/[id]/info/materials/page.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { searchParamsMaterialCache } from "@/lib/vendors/validations"
-import { getVendorMaterials } from "@/lib/vendors/service"
-import { VendorMaterialsTable } from "@/lib/vendors/materials-table/item-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function SettingsAccountPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsMaterialCache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
-
-
- const promises = Promise.all([
- getVendorMaterials({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- 공급품목(자재 그룹)
- </h3>
- <p className="text-sm text-muted-foreground">
- {/* 딜리버리가 가능한 공급품목(자재 그룹)을 확인할 수 있습니다. */}
- </p>
- </div>
- <Separator />
- <div>
- <VendorMaterialsTable promises={promises} vendorId={idAsNumber}/>
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/vendors/[id]/info/page.tsx b/app/[lng]/procurement/(procurement)/vendors/[id]/info/page.tsx
deleted file mode 100644
index 6279e924..00000000
--- a/app/[lng]/procurement/(procurement)/vendors/[id]/info/page.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { getVendorContacts } from "@/lib/vendors/service"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { searchParamsContactCache } from "@/lib/vendors/validations"
-import { VendorContactsTable } from "@/lib/vendors/contacts-table/contact-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function SettingsAccountPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsContactCache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
-
-
- const promises = Promise.all([
- getVendorContacts({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- Contacts
- </h3>
- <p className="text-sm text-muted-foreground">
- 업무별 담당자 정보를 확인하세요.
- </p>
- </div>
- <Separator />
- <div>
- <VendorContactsTable promises={promises} vendorId={idAsNumber}/>
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/vendors/[id]/info/rfq-history/page.tsx b/app/[lng]/procurement/(procurement)/vendors/[id]/info/rfq-history/page.tsx
deleted file mode 100644
index c7f8f8b6..00000000
--- a/app/[lng]/procurement/(procurement)/vendors/[id]/info/rfq-history/page.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Separator } from "@/components/ui/separator"
-import { getRfqHistory } from "@/lib/vendors/service"
-import { type SearchParams } from "@/types/table"
-import { getValidFilters } from "@/lib/data-table"
-import { searchParamsRfqHistoryCache } from "@/lib/vendors/validations"
-import { VendorRfqHistoryTable } from "@/lib/vendors/rfq-history-table/rfq-history-table"
-
-interface IndexPageProps {
- // Next.js 13 App Router에서 기본으로 주어지는 객체들
- params: {
- lng: string
- id: string
- }
- searchParams: Promise<SearchParams>
-}
-
-export default async function RfqHistoryPage(props: IndexPageProps) {
- const resolvedParams = await props.params
- const lng = resolvedParams.lng
- const id = resolvedParams.id
-
- const idAsNumber = Number(id)
-
- // 2) SearchParams 파싱 (Zod)
- // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
- const searchParams = await props.searchParams
- const search = searchParamsRfqHistoryCache.parse(searchParams)
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getRfqHistory({
- ...search,
- filters: validFilters,
- },
- idAsNumber)
- ])
-
- // 4) 렌더링
- return (
- <div className="space-y-6">
- <div>
- <h3 className="text-lg font-medium">
- RFQ History
- </h3>
- <p className="text-sm text-muted-foreground">
- 협력업체의 RFQ 참여 이력을 확인할 수 있습니다.
- </p>
- </div>
- <Separator />
- <div>
- <VendorRfqHistoryTable promises={promises} />
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/procurement/(procurement)/vendors/page.tsx b/app/[lng]/procurement/(procurement)/vendors/page.tsx
deleted file mode 100644
index 02616999..00000000
--- a/app/[lng]/procurement/(procurement)/vendors/page.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-
-
-import { searchParamsCache } from "@/lib/vendors/validations"
-import { getVendors, getVendorStatusCounts } from "@/lib/vendors/service"
-import { VendorsTable } from "@/lib/vendors/table/vendors-table"
-import { Ellipsis } from "lucide-react"
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- const promises = Promise.all([
- getVendors({
- ...search,
- filters: validFilters,
- }),
- getVendorStatusCounts(),
- ])
-
- return (
- <Shell className="gap-2">
-
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 협력업체 관리
- </h2>
- {/* <p className="text-muted-foreground">
- 협력업체에 대한 요약 정보를 확인하고{" "}
- <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span>
- 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. <br/>벤더의 상태에 따라 가입을 승인해주거나 PQ 요청을 할 수 있고 검토가 완료된 벤더를 기간계 시스템에 전송하여 협력업체 코드를 따올 수 있습니다.
- </p> */}
- </div>
- </div>
- </div>
-
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <VendorsTable promises={promises} />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/page.tsx b/app/[lng]/procurement/page.tsx
deleted file mode 100644
index f9662cb7..00000000
--- a/app/[lng]/procurement/page.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Metadata } from "next"
-import { Suspense } from "react"
-import { LoginFormSkeleton } from "@/components/login/login-form-skeleton"
-import { LoginFormSHI } from "@/components/login/login-form-shi"
-
-export const metadata: Metadata = {
- title: "eVCP Portal",
- description: "",
-}
-
-export default function AuthenticationPage() {
-
-
- return (
- <>
- <Suspense fallback={<LoginFormSkeleton/>}>
- <LoginFormSHI />
- </Suspense>
- </>
- )
-}